вторник, 19 августа 2014 г.

Коротко. О коде и "так бывает"

Сегодня добрался до одного старинного кода, который был написан лет пятнадцать назад и "вполне исправно работал", но "крайне иногда" он глючил и "бил данные".

При этом код работал под вполне себе приличной нагрузкой. В распределённой и многопоточной среде.

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

Причём при "детальном рассмотрении" кода - ошибка оказалась "очевидной".

Когда я увидел проблемное место - я сразу хлопнул себя по лбу со словами - "очевидно же, что тут надо защищать ресурс".

При этом не хватало не только критической секции, но и "сетевого" 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.

При этом такие вещи как список директории, удаление элемента, добавление нового, переименование - прозрачно работают как для "внешних" утилит, так и для "внутренних".

Просто "внутренние" иногда знают "более короткий путь".

Комментариев нет:

Отправить комментарий