LINUX.ORG.RU

можно ли надёжно записать на диск?


0

2

Разрабатываю приложение которое должно быть устойчивым к сбоям. Производительность на первом этапе не нужна, главное чтобы данные не терялись.

Вопрос такой: могу ли я быть уверенным что после flush && fsync всё будет надёжно сохранено и сбой питания не проблема?

Аппаратные проблемы и работу в виртуализированном окружении пока не беру.

★★★★★

man fsync

If the underlying hard disk has write caching enabled, then the data may not really be on permanent storage when fsync() / fdatasync() return.

anonymous
()
Ответ на: комментарий от anonymous

Так я и думал. Есть ли надёжный способ а) выяснить что это кэширование включено б) побороть его?

Случай с внешними стораджами и хитрожопыми рейдами(для которых законы не писаны) пока не рассматриваю.

true_admin ★★★★★
() автор топика
Ответ на: комментарий от true_admin

Производители СУБД как-то решили это проблему с кэшами дисков и другими пакостями при записи на диск, так как команда COMMIT должна давать 100% уверенность, что всё OK. ‌Исходники их посмотрите :)

anonymous
()
Ответ на: комментарий от true_admin

Это уже не совсем вопросы приложения. Ты уверен что не хочешь странного?

vasily_pupkin ★★★★★
()
Ответ на: комментарий от true_admin

> If the underlying hard disk has write caching enabled, then the data may not really be on permanent storage when fsync() / fdatasync() return.

Это относится к случаю, когда драйвер диска не умеет сбрасывать кэш контроллера на блины. Но в этом случае ничего сделать нельзя.

tailgunner ★★★★★
()
Ответ на: комментарий от Eshkin_kot

> Можно отключить кеш :)

Уверенно сказал? Можно, на любом контроллере, любого производителя?

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

> и как переключить кэш диска в режим сквозной записи?

hdparm -W 0 /dev/sda

Eshkin_kot ★★
()
Ответ на: комментарий от anonymous

Производители СУБД как-то решили это проблему

Там другая ситуация. Во-первых, есть сомнение что всё решили ибо я видел битые базы. Во-вторых, они решали проблему консистентности данных(в mysql, например, через двойную буферизацию на диск), а вот на счёт того что последняя транзакция не потеряется это ещё не факт. Ну и тут от настроек многое зависит. innodb_flush_log_at_trx_commit=0 и привет :).

А мне нельзя терять данные.

true_admin ★★★★★
() автор топика
Ответ на: комментарий от arsi

мб «hdparm -F» подойдёт?

мне бы наверняка знать что надо делать :). Конечно, это не повредит, но оно 1) требует рута 2) вызывать пару десятков раз в секунду не очень хочется. Хотя можно расковырять исходники и найти нужный sysctl... Но тут мне подсказывают hdparm -W 0 /dev/sda... Только непонятно как оно работает с софтварными рейдами и насколько дрова к этому готовы...

Ну чтож, за неимением лучшего может так и сделаю.

true_admin ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Насколько я помню коммит просто закрывает транзакцию. Если данные не успели попасть на диск, то при след запуске СУБД обнаружит незавершенную транзакцию и откатит её или докатит, смотря по обстоятельствам.

Некоторые СУБД могут работать на raw-устройствах мимо кешей ОС, но от аппаратного кеширования записи это не спасёт.

sergej ★★★★★
()
Ответ на: комментарий от sergej

Насколько я помню коммит просто закрывает транзакцию

от настроек зависит. Как раз innodb_flush_log_at_trx_commit это и тюнит у мускля. Но я буду использовать sqlite скорее всего, strace показывает что там fdatasync() используется.

true_admin ★★★★★
() автор топика
Ответ на: комментарий от true_admin

ну… заведи себе левый файл на том же диске объёмом не меньше кэша винта, и забивай его рандомом каждый раз, когда нужно будет кэш записи сбросить %)

arsi ★★★★★
()
Ответ на: комментарий от arsi

я уже решил что ценные данные не должны храниться на одном хосте и это, скорее всего, будет реплицируемая key-value in-memory бд :). Возможно буду использовать оба метода(синхронная репликация+сброс на диск), пока не решил.

true_admin ★★★★★
() автор топика

flush + fsync - работает не от рута. это гуд. и даёт гарантию на которую полагаются все СУБД.

Для более-менее надёжного сброса буфферов есть команда blockdev --flushbufs (ioctl BLKFLSBUF) но работает тока для тех кто может открыть /dev/диск

Причём работает только на сыром девайсе типа /dev/sda но не на /dev/sda1 и никак не на LVM и не на RAID. Причём даже если ioctl сказал OK - можете нарваться на эмуляцию оного в ведре (ну типа чтобы софт не глючил, понимаете же).

Ещё, по теме — sync() работает только на данные, записанные через файловую систему. Если обращение было через /dev/чтонибудь то sync() это не синкает.

mmarkk
()
Ответ на: комментарий от xorik

O_DIRECT для этих целей вроде как существует

Во-первых, не для этих целей он существует :)

Во-вторых, он требует выравнивания смещения и длины по секторам (или страницам? не помню)

В третьих

В четвёртых, он эффективен только если с этим флагом открывается блочный девайс. для файлов он оставлен для совместимости с дерьмософтом.

mmarkk
()
Ответ на: комментарий от tailgunner

Стандарт обещает именно это

Ну-ну :)

If _POSIX_SYNCHRONIZED_IO is not defined, the wording relies heavily on the conformance document to tell the user what can be expected from the system. It is explicitly intended that a null implementation is permitted. This could be valid in the case where the system cannot assure non-volatile storage under any circumstances or when the system is highly fault-tolerant and the functionality is not required. In the middle ground between these extremes, fsync() might or might not actually cause data to be written where it is safe from a power failure. The conformance document should identify at least that one configuration exists (and how to obtain that configuration) where this can be assured for at least some files that the user can select to use for critical data. It is not intended that an exhaustive list is required, but rather sufficient information is provided so that if critical data needs to be saved, the user can determine how the system is to be configured to allow the data to be written to non-volatile storage.

It is reasonable to assert that the key aspects of fsync() are unreasonable to test in a test suite. That does not make the function any less valuable, just more difficult to test. A formal conformance test should probably force a system crash (power shutdown) during the test for this condition, but it needs to be done in such a way that automated testing does not require this to be done except when a formal record of the results is being made. It would also not be unreasonable to omit testing for fsync(), allowing it to be treated as a quality-of-implementation issue.

(хотя BtrFS вроед игнорирует fsync).

А как ты думаешь, зачем в ней так называемое Log Tree имеется?

mukoh
()
Ответ на: комментарий от anonymous

> Производители СУБД как-то решили это проблему с кэшами дисков и другими пакостями при записи на диск, так как команда COMMIT должна давать 100% уверенность, что всё OK. ‌Исходники их посмотрите :)

Никак они ее не решили, они полагаются на fsync(). Но как говорится, «доверяй, но проверяй», поэтому логи снабжаются разного рода проверочной информацией, которая при их накатывании проверяется.

mukoh
()
Ответ на: комментарий от tailgunner

> Это относится к случаю, когда драйвер диска не умеет сбрасывать кэш контроллера на блины.

Не только к этому случаю. Это может быть и блочный уровень, и фаловая система, короче, любой компонент стека ввода-вывода на пути от приложения до пластины диска. При этом все остальные компоненты могуть быть все из себя правильные и корректные.

mukoh
()
Ответ на: комментарий от mukoh

>> Это относится к случаю, когда драйвер диска не умеет сбрасывать кэш контроллера на блины.

Не только к этому случаю.

Из известных мне - только к этому.

Это может быть и блочный уровень, и фаловая система, короче, любой компонент стека ввода-вывода на пути от приложения до пластины диска

Примеры - в студию (кроме ошибок).

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> Примеры - в студию (кроме ошибок).

Напомни, когда там поддержку барьеров в LVM реализовали? Или это тоже ошибка была?

И да, ты таки считаешь, что на ошибки можно не обращать внимания?

mukoh
()
Ответ на: комментарий от mukoh

> Напомни, когда там поддержку барьеров в LVM реализовали?

В 2.6.32 вроде бы.

Или это тоже ошибка была?

По-моему, на эту тему даже был баг в Bugzilla, так что это ошибка, конечно (и, ЕМНИП, она выражалась в коде ошибки вроде «операция не поддерживается»).

И да, ты таки считаешь, что на ошибки можно не обращать внимания?

Обращать нужно внимание только на ошибки, которые можно исправить или обнаружить.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> По-моему, на эту тему даже был баг в Bugzilla, так что это ошибка, конечно (и, ЕМНИП, она выражалась в коде ошибки вроде «операция не поддерживается»).

Не суть важно. Важно то, что LVM это не драйвер диска, и стоило всунуть LVM в стэк, как про барьеры можно было забыть, при том, что все остальное их умело, и без LVM все работало как надо. А ведь об этом далеко не каждый знал.

mukoh
()
Ответ на: комментарий от mmarkk

>В четвёртых, он эффективен только если с этим флагом открывается

блочный девайс. для файлов он оставлен для совместимости с
дерьмософтом.

Помимо некомпетентного мнения Торвальдса, на которое вы дали ссылку, какие-нибудь обоснования этого тезиса имеются?

O_DIRECT используется в огромном количестве приложений, где абсолютно не требуется: 1) засирание памяти и вытеснение полезной части page cache теми данными, которые попадут в буфер пользовательского приложения и будут использоваться только из него; 2) бесполезные копирования данных туда-сюда, проедающие ценное процессорное время.

Для запуска сапера это, конечно, не особо важно, но для HPC-приложений еще как важно.

Впрочем, даже для домашнего пользования это еще как актуально местами:

# echo 3 > /proc/sys/vm/drop_caches

# dd if=/dev/sda of=/dev/null bs=10M count=100 iflag=direct

100+0 records in

100+0 records out

1048576000 bytes (1.0 GB) copied, 4.74377 s, 221 MB/s

# dd if=/dev/sda of=/dev/null bs=10M count=100

100+0 records in

100+0 records out

1048576000 bytes (1.0 GB) copied, 5.07283 s, 207 MB/s

Подумайте на досуге, как можно на практике воспользоваться этой фишкой «дерьмософта» dd.

anonymous
()
Ответ на: комментарий от mmarkk

>Для более-менее надёжного сброса буфферов есть команда blockdev --flushbufs (ioctl BLKFLSBUF) но работает тока для тех кто может открыть /dev/диск

Никогда раньше не сталкивался с этим ioctl. Ради интереса сделал grep по 2.6.35 (все написанное ниже верно для него) - он по сути сводится к fsync_bdev. Формально есть вызов ioctl нижележащего драйвера, но никто не обрабатывает BLKFLSBUF, либо обрабатывает не так, как вы хотите.

fsync_bdev честно все отправляет на запись, но не делает ничего, чтобы реально сбросить device write cache на блины (или другую media).

Насколько я могу сходу судить, можно использовать fsync(file_fd) для сброса данных, но при наличии ряда условий:
1) метод fsync у ФС вызывает blkdev_issue_flush() (это ext3, ext4, reiserfs, xfs)
2) ФС смонтирована с соответствующей опцией (barrier)
3) драйвер поддерживает BARRIER
4) контроллер поддерживает команду сброса
5) устройство поддерживает команду сброса

Пункты 1 и 2 по идее можно не выполнять, если fsync(file_fd) заменить на dev_fd = open(«/dev/...»,...); fsync(dev_fd). По какой-то причине sync(2) в отличие от fsync на дескриптор устройства не сделает запрос на барьер. Возможно проглядел этот код, но так сходу он там не виден...

anonymous
()
Ответ на: комментарий от anonymous

>anonymous (16.07.2011 4:08:35)

P.S. Существуют, конечно, разнообразные костыли, позволяющие частично обойти проблемы навязанного кэша. Например, можно использовать memory mapping, чтобы избежать копирования. Это избавит от проблем лишнего копирования, но не избавит от проблемы вытеснения полезного страничного кэша новыми данными. Всякие fadvise/madvise, позволяющие «убивать» ненужный кэш и частично избежать проблем вытеснения. Вопрос только в том, зачем нужны эти уродливые костыли, когда есть простой и понятный O_DIRECT, позволяющий процессу полностью самому управлять кэшированием...

anonymous
()
Ответ на: комментарий от mi_estas

>нет. как минимум у винта ещё аппаратный кеш есть. от 8ми метров.

давно уже все винты умеют сбрасывать кэш на диск при пропадании питания, используя кинетическую энергию вращения блинов для выработки аварийного электропитания электроники винта.

anonymous
()
Ответ на: комментарий от anonymous

Помимо некомпетентного мнения Торвальдса, на которое вы дали ссылку,

какие-нибудь обоснования этого тезиса имеются?

Лично меня вполне убедило.

Интересно, как же работает O_DIRECT если файловая система зашифрована? или как же он работает в случае, когда концы файлов объединены (как в reiser)? или, другими словами, что насчёт тех случаев, когда нельзя через ФС найти для куска файла один или несколько кусков на HDD? тогда, получается, надо какие-то промежуточные копирования делать... Я уж не говорю про FUSE, NFS и т.д. а нет никаких гарантий насчёт того на какой ФС будет размещён файл БД.

O_DIRECT используется в огромном количестве приложений

Я же говорю - для совместимости. этот флаг более кроссплатформенен, в отличие от fadvice/madvice. Вот sqlite почему-то обходится, и ничего, быстро и надёжно.

В старые времена, вместо (open, fadvice, write, fsync) использовали (open+O_DIRECT, write) но это уже люто устарело - ФС не те, да и барьеры не используются.

засирание памяти и вытеснение полезной части page cache теми данными, которые попадут в буфер пользовательского приложения и будут использоваться только из него

mmap + madvice

бесполезные копирования данных туда-сюда, проедающие ценное процессорное время

один фиг, IO сегодня работает только через mmap. только при обычном read/write есть лишнее копирование. при mmap его не происходит. Естественно, за исключением случаем с ацкими извращами в ФС, типа reiser, NFS, FUSE или шифрования.

Подумайте на досуге, как можно на практике воспользоваться этой фишкой «дерьмософта» dd

подeмал. и решил что dd не использует mmap

mmarkk
()
Ответ на: комментарий от anonymous

Подумайте на досуге, как можно на практике воспользоваться этой фишкой «дерьмософта» dd.

Да, я уже говорил что для блочных устройств работа через O_DIRECT оправдана (позволяет обойтись read/write без mmap и лишнего копирования при этом не будет). Например, qemu с cache=off и использовании блочного устройства в качестве содержимого вирт. диска как раз использует O_DIRECT и это гуд. Я же против O_DIRECT на файлы.

mmarkk
()
Ответ на: комментарий от mmarkk

Вот sqlite почему-то обходится, и ничего, быстро и надёжно.

потому что у него нет своих кэшей. Если они O_DIRECT сделают то будет жутко медленно.

Я же против O_DIRECT на файлы.

Из-за того что «файловая система зашифрована»? Это уже проблема пользователя, в таких случаях O_DIRECT должен вернуть ошибку и всё.

Польза от O_DIRECT есть и для файлов, причины в том что кэш не вытесняется. На серверах это бывает весьма заметно.

true_admin ★★★★★
() автор топика
Ответ на: комментарий от true_admin

Из-за того что «файловая система зашифрована»? Это уже проблема пользователя, в таких случаях O_DIRECT должен вернуть ошибку и всё.

  1. То есть на reiserfs должно возвращать ошибку?
  2. sparse-файлы и O_DIRECT? — если чювак сделает lseek вперёд, а потом туда обратится - IOERROR возвращать?
  3. Что насчёт ftruncate() в другом процессе ? mmap даст bus error, а тут?
  4. delayed allocation? O_DIRECT тут неприменим.
  5. Представляешь, сколько сложностей добавляет O_DIRECT при онлайн дефрагментации? юмор в том что доступ к данным происходит как-бы мимо ФС.
  6. Представляешь, сколько проблем при одновременном доступе к файлу с O_DIRECT и без O_DIRECT ? я имею в виду синхронизацию кешей.

Конечно, можно всё сливать на админа и кривые программы но это же не Ъ.

Польза от O_DIRECT есть и для файлов, причины в том что кэш не вытесняется

При fadvice тоже не вытесняется.

Да, я у себя провел эксперимент с

echo 3 > /proc/sys/vm/drop_caches; dd if=/dev/sda of=/dev/null bs=10M count=100

Никакого прироста я не заметил. В твоём же примере, я думаю, во всём виновата погрешность. Проведи тест с bs=10M count=1000.

Тест dd на нефрагментированный (для уменьшения погрешности) файл — в этом случае, прироста скорости я тоже не заметил.

mmarkk
()
Ответ на: комментарий от true_admin

Польза от O_DIRECT есть и для файлов

Да, для sparse-файлов польза есть:

root@imac:/home# truncate -s 500M test
root@imac:/home# dd if=test of=/dev/null bs=10M
50+0 записей считано
50+0 записей написано
скопировано 524288000 байт (524 MB), 0,581432 c, 902 MB/c
root@imac:/home#
root@imac:/home# dd if=test of=/dev/null bs=10M iflag=direct
50+0 записей считано
50+0 записей написано
скопировано 524288000 байт (524 MB), 0,275208 c, 1,9 GB/c

причём, ровно в 2 раза. да, диск тут не при чём. ступор в этом случае именно с memcpy. доказано.

mmarkk
()
Ответ на: комментарий от mmarkk

>Да, я уже говорил что для блочных устройств работа через O_DIRECT оправдана (позволяет обойтись read/write без mmap и лишнего копирования при этом не будет). Например, qemu с cache=off и использовании блочного устройства в качестве содержимого вирт. диска как раз использует O_DIRECT и это гуд. Я же против O_DIRECT на файлы.

В данном случае нет никакой разницы. Примеры с шифрованием и хвостами - надуманные и погоды не делают.

Никакого прироста я не заметил.


Тогда стоит задуматься о смене дисков или усложнении примера.

В твоём же примере, я думаю, во всём виновата погрешность. Проведи тест с bs=10M count=1000.


# echo 3 > /proc/sys/vm/drop_caches; dd if=/dev/sda of=/dev/null bs=10M count=1000 iflag=direct; dd if=/dev/sda of=/dev/null bs=10M count=1000
1000+0 records in
1000+0 records out
10485760000 bytes (10 GB) copied, 46.9284 s, 223 MB/s
1000+0 records in
1000+0 records out
10485760000 bytes (10 GB) copied, 50.261 s, 209 MB/s

anonymous
()
Ответ на: комментарий от anonymous

>давно уже все винты умеют сбрасывать кэш на диск при пропадании питания, используя кинетическую энергию вращения блинов для выработки аварийного электропитания электроники винта.

Экспериментальную проверку данное утверждение не выдерживает. :-)

anonymous
()
Ответ на: комментарий от anonymous

окей. Видимо, действительно, на моём аймаке медленная память.

ИМХО, получается, что из плюсов O_DIRECT — повышение скорости, а из минусов — всё что я наговорил.

Учитывая топик, O_DIRECT лучше не юзать для этих задач.

mmarkk
()
Ответ на: комментарий от anonymous

давно уже все винты умеют сбрасывать кэш на диск при пропадании питания

Очень интересно. А если в кеше 16 МБ сильнофрагментированных данных - оно точно сумеет их записать на останавливающиеся блины ?

вот что думает Intel об этом

mmarkk
()

А не лучше использовать бесперебойник и выключение по сигналу от nut ?

Как уже сказали, кэш дисков отключается через hdparm/sdparm, у аппаратных рейдов свои настройки.

router ★★★★★
()
Ответ на: комментарий от mmarkk

>То есть на reiserfs должно возвращать ошибку?

Эта внутренняя проблема^W^W особенность reiserfs может привести и к другим проблемам. Например, не будет работать bmap. mmap, если и будет работать, то кривым способом (не через DMA, а через копирование, через которое можно реализовать и direct i/o). Хвосты отключаются без проблем. Тем более, что пользы от них никакой.

sparse-файлы и O_DIRECT? — если чювак сделает lseek вперёд, а потом туда обратится - IOERROR возвращать?

Не понял примера. Чтение из «дырки» должно давать нули. Любым read.

Что насчёт ftruncate() в другом процессе ? mmap даст bus error, а тут?

Опять какой-то непонятный пример. Тут будет взависимости от очередности read/truncate. Как и полагается. Bus error - это чисто фишка mmap, которая к read/O_DIRECT никаким боком.

delayed allocation? O_DIRECT тут неприменим.

Очевидно, что если приложение выбирает самостоятельное управление кэшем, то оно должно уметь это делать мало-мальски эффективно. Если оно будет писать или читать по 4 кб, то автор сам себе злобный буратино.

Представляешь, сколько проблем при одновременном доступе к файлу с O_DIRECT и без O_DIRECT ? я имею в виду синхронизацию кешей.

Любой не ro одновременный доступ к файлу требует тех или иных протоколов синхронизации.

Представляешь, сколько сложностей добавляет O_DIRECT при онлайн дефрагментации? юмор в том что доступ к данным происходит как-бы мимо ФС.

direct_IO() - это обычный метод ФС, в котором можно использовать любые механизмы синхронизации. Можно использовать generic blockdev функцию, но это не обязательно. А для сетевых ФС и вовсе неприменимо.

anonymous
()
Ответ на: комментарий от anonymous

а если молотоком прямо по корпусу например?

шучу.

mi_estas
()
Ответ на: комментарий от mmarkk

Вопросов ты много поднял, но у меня на них один ответ: если ядро по каким-то причинам не может гарантировать корректное поведение O_DIRECT то должна возвращаться ошибка.

Это решает все проблемы, например, с обеспечением когерентностей кэшей.

Т.е. я на полном серьёзе считаю что если файл уже открыт с O_DIRECT то повторное открытие этого файла без этого флага должно быть запрещено. Тем более что вряд ли такой «микс» понадобится в реальном продакшене.

Да, это не совсем гибкая конфигурация, зато поведение строго детерминировано. Я считаю что самая большая проблема современных юниксов это слабая детерминированность поведения и куча deprecated механизмов.

true_admin ★★★★★
() автор топика
Ответ на: комментарий от anonymous

используя кинетическую энергию вращения блинов для выработки аварийного электропитания электроники винта.

кстати, я в это не верю. Имхо это байка потому что скорость вращения винта должна быть ооочень стабильной. Ты можешь найти данные о точности в инете. Замедление даже на процент это хана данным. Поэтому вряд ли ты с него сколько-нибудь энергии снимешь.

Так же вызывает сомнение то что те моторчики что там стоят можно использовать как генераторы. Во всяком случае это не так просто, там синхронный двигатель.

true_admin ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Не понял примера. Чтение из «дырки» должно давать нули. Любым read.

А запись в режиме O_DIRECT как?

Bus error - это чисто фишка mmap, которая к read/O_DIRECT никаким боком.

Один процесс начал IO с O_DIRECT а другой сделал truncate. в итоге производится IO с областью диска, не принадлежащей файлу. у mmap такой проблемы нет - сразу егор.

под delayed allocation я имел в виду, что если файл ещё недозаписан с включенным этим режимом - то O_DIRECT в этим места весьма не определен.

mmarkk
()
Ответ на: комментарий от true_admin

если ядро по каким-то причинам не может гарантировать корректное поведение O_DIRECT то должна возвращаться ошибка

А если оно может, но в некоторых областях файла - не может?

В общем, я считаю, что в твоей ситуации (где не было вопросов про производительность), правильно использовать write+fdatasync — он даёт столько же гарантий сколько и O_DIRECT.

mmarkk
()
Ответ на: комментарий от mmarkk

>А запись в режиме O_DIRECT как?

Запись выделяет место. Если выполнится до truncate, то может перезаписать уже выделенные блоки. Если после truncate, то выделит новые.

Один процесс начал IO с O_DIRECT а другой сделал truncate. в итоге производится IO с областью диска, не принадлежащей файлу. у mmap такой проблемы нет - сразу егор.

truncate и write будут сериализованы через i_alloc семафор. SIGBUS - это исключительно реакция для пользовательского приложения. С концептуальной точки зрения она не обеспечивает корректность описанного вами race condition.

под delayed allocation я имел в виду, что если файл ещё недозаписан с включенным этим режимом - то O_DIRECT в этим места весьма не определен.

Если файл недозаписан, то значит используется одновременно O_DIRECT и кэшированный доступ, что, вообще говоря, некорректно без спец. протокола синхронизации.

Но неконсистентности данных не будет. Все записи сериализуются с помощью i_mutex. После взятия i_mutex данные из кэша будут записаны, затем будет выполнен write/O_DIRECT запрос, затем отпущен i_mutex.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.