Сегодня добрался до одного старинного кода, который был написан лет пятнадцать назад и "вполне исправно работал", но "крайне иногда" он глючил и "бил данные".
При этом код работал под вполне себе приличной нагрузкой. В распределённой и многопоточной среде.
Так вот - оказалось, что в этом коде не хватало использования критической секции. Ну т.е. были куски кода, где данные не защищались от многопоточного использования.
Причём при "детальном рассмотрении" кода - ошибка оказалась "очевидной".
Когда я увидел проблемное место - я сразу хлопнул себя по лбу со словами - "очевидно же, что тут надо защищать ресурс".
При этом не хватало не только критической секции, но и "сетевого" LockRegion'а на часть файла, который служит "объектом синхронизации" между несколькими распределёнными пользователями, которые не должны обращаться к ресурсу одновременно. По крайней мере на запись.
Я вставил использование критической секции и LockRegion.
На "синтетическом примере" - вроде бы стало "сильно лучше".
К сожалению реальных примеров "из жизни пользователей", которые бы воспроизводились - у меня нет. Поэтому говорить о том, что я "починил ошибку" - ещё очень и очень рано.
Выложу новый код пользователям и "буду за ним пристально наблюдать".
На тестах - всё работает. У реальных пользователей - пока не знаю.
"Буду наблюдать". Надеюсь, что "починил". Ну по крайней мере - ошибка теперь кажется "очевидной".
К чему это я всё?
А к тому, что "очень удивительно", что этот код уже пятнадцать лет как работает и "глючил" - очень и очень редко.
Это наверное - "теория вероятности". :-)
Да и чтобы меня не обвинили в "экспериментах над пользователями" - оговорюсь, что "реальные пользователи" - это не те, которым мы продаём продукт, а те которые "работают внутри". В частности (и в первую очередь) - тестировщики. Которым "по должности" положено искать ошибки.
А над "совсем реальными пользователями" - мы конечно же "экспериментов не ставим".
И к вопросу о том, "что это за велосипед" - поясню - да это "велосипед", собственная реализация IStorage и IStream (по индукции). И этот "велосипед" - работает. Уже лет пятнадцать как. Правда иногда "глючит".
Почему не было использовать "стандартный велосипед"? Потому, что - он не удовлетворял нашим требованиям. По нагрузке и многопоточности - в частности.
Почему мы вообще связались с IStream, а не выдумали свой велосипед с самого начала?
Потому, что для IStream было (и есть) множество "сторонних разработок". В частности от того же MS.
Ну и "самое вкусное" это - StgFileBrowser для FAR'а, которым мы пользуемся с удовольствием.
Правда "до конца" нам в итоге не получилось "выдержать чистоту арийской расы" интерфейса IStorage.
Пришлось вводить дополнительный интерфейс:
-- но это не вместо IStorage, а вместе с ним. Как "расширение", когда "сильно важна" производительность.
Всё стандартные утилиты ходят лишь через IStorage, а наши внутренние ходят как через IStorage, так и через Im3IndexedStorage.
При этом такие вещи как список директории, удаление элемента, добавление нового, переименование - прозрачно работают как для "внешних" утилит, так и для "внутренних".
Просто "внутренние" иногда знают "более короткий путь".
При этом код работал под вполне себе приличной нагрузкой. В распределённой и многопоточной среде.
Так вот - оказалось, что в этом коде не хватало использования критической секции. Ну т.е. были куски кода, где данные не защищались от многопоточного использования.
Причём при "детальном рассмотрении" кода - ошибка оказалась "очевидной".
Когда я увидел проблемное место - я сразу хлопнул себя по лбу со словами - "очевидно же, что тут надо защищать ресурс".
При этом не хватало не только критической секции, но и "сетевого" LockRegion'а на часть файла, который служит "объектом синхронизации" между несколькими распределёнными пользователями, которые не должны обращаться к ресурсу одновременно. По крайней мере на запись.
Я вставил использование критической секции и LockRegion.
На "синтетическом примере" - вроде бы стало "сильно лучше".
К сожалению реальных примеров "из жизни пользователей", которые бы воспроизводились - у меня нет. Поэтому говорить о том, что я "починил ошибку" - ещё очень и очень рано.
Выложу новый код пользователям и "буду за ним пристально наблюдать".
На тестах - всё работает. У реальных пользователей - пока не знаю.
"Буду наблюдать". Надеюсь, что "починил". Ну по крайней мере - ошибка теперь кажется "очевидной".
К чему это я всё?
А к тому, что "очень удивительно", что этот код уже пятнадцать лет как работает и "глючил" - очень и очень редко.
Это наверное - "теория вероятности". :-)
Да и чтобы меня не обвинили в "экспериментах над пользователями" - оговорюсь, что "реальные пользователи" - это не те, которым мы продаём продукт, а те которые "работают внутри". В частности (и в первую очередь) - тестировщики. Которым "по должности" положено искать ошибки.
А над "совсем реальными пользователями" - мы конечно же "экспериментов не ставим".
И к вопросу о том, "что это за велосипед" - поясню - да это "велосипед", собственная реализация IStorage и IStream (по индукции). И этот "велосипед" - работает. Уже лет пятнадцать как. Правда иногда "глючит".
Почему не было использовать "стандартный велосипед"? Потому, что - он не удовлетворял нашим требованиям. По нагрузке и многопоточности - в частности.
Почему мы вообще связались с IStream, а не выдумали свой велосипед с самого начала?
Потому, что для IStream было (и есть) множество "сторонних разработок". В частности от того же MS.
Ну и "самое вкусное" это - StgFileBrowser для FAR'а, которым мы пользуемся с удовольствием.
Правда "до конца" нам в итоге не получилось "выдержать чистоту арийской расы" интерфейса IStorage.
Пришлось вводить дополнительный интерфейс:
Im3IndexedStorage = interface(IStorage) {* Хранилище с возможностью доступа по индексу. } ['{1962E532-4615-4D4A-9FAC-0F1E3402F097}'] function SetIndexParam(aBits: byte; aMaxBits: byte): Boolean; {* устанавливает параметры "размазывания" индекса. } function DeleteStore(anIndex: Integer): hResult; {* удаляет элемент хранилища. } function CreateStore(anIndex: Integer; anAccess: Tm3StoreAccess; aStoreType: Tm3StoreType; out aStore: IUnknown; aUseCompression: Boolean = true): hResult; overload; {* создает элемент хранилища. } function OpenStore(anIndex: Integer; anAccess: Tm3StoreAccess; aStoreType: Tm3StoreType; out aStore: IUnknown; aUseCompression: Boolean = true): hResult; overload; {* открывает элемент хранилища. } function CreateStore(const aName: Tl3PCharLen; anAccess: Tm3StoreAccess; aStoreType: Tm3StoreType; out aStore: IUnknown; aUseCompression: Boolean = true): hResult; overload; {* создает элемент хранилища } function OpenStore(const aName: Tl3PCharLen; anAccess: Tm3StoreAccess; aStoreType: Tm3StoreType; out aStore: IUnknown; aUseCompression: Boolean = true): hResult; overload; {* открывает элемент хранилища } function OpenStore(aPosition: Int64; anAccess: Tm3StoreAccess; const aName: Tl3PCharLen; aStoreType: Tm3StoreType; aUseCompression: Boolean = true): IUnknown; overload; {* открывает элемент хранилища. } function OpenStore(aPosition: Int64; anAccess: Tm3StoreAccess; anIndex: Integer; aStoreType: Tm3StoreType; aUseCompression: Boolean = true): IUnknown; overload; {* открывает элемент хранилища } function OpenStore(const aStoreInfo: Tm3StoreInfo; const aName: Tl3PCharLen; anAccess: Tm3StoreAccess = m3_saRead; aUseCompression: Boolean = true): IUnknown; overload; {* открывает элемент хранилища. } procedure Iterate(anAction: Tm3StorageElementAction); overload; {* перебирает элементы хранилища по именам. } procedure Iterate(anAction: Tm3StoreAction); overload; {* перебирает элементы хранилища по индексам. } procedure IterateF(anAction: Tm3StorageElementAction); overload; {* перебирает элементы хранилища, потом освобождает заглушку. } procedure IterateF(anAction: Tm3StoreAction); overload; {* перебирает элементы хранилища, потом освобождает заглушку. } function RenameElementA(const aOldName: Tl3WString; const aNewName: Tl3WString): hResult; {* Переименовывает элемент хранилища } function ElementExists(const aName: Tl3WString): Boolean; {* Проверяет существование элемента с указанным именем } end;//Im3IndexedStorage
-- но это не вместо IStorage, а вместе с ним. Как "расширение", когда "сильно важна" производительность.
Всё стандартные утилиты ходят лишь через IStorage, а наши внутренние ходят как через IStorage, так и через Im3IndexedStorage.
При этом такие вещи как список директории, удаление элемента, добавление нового, переименование - прозрачно работают как для "внешних" утилит, так и для "внутренних".
Просто "внутренние" иногда знают "более короткий путь".
Комментариев нет:
Отправить комментарий