SASGIS - SAS.Планета
View Issue Details
0003029SAS.Планета[All Projects] Багpublic19-05-2016 10:1019-05-2016 14:39
zed 
zed 
normalminoralways
resolvedfixed 
.Nightly 
160606160606 
0003029: AV при уменьшении числа MaxConnectToServerCount из настроек карты
Если в настройках карты увеличить число качающих потоков, то всё ОК, но если потом попытаться их уменьшить, то вываливается исключение с попыткой обратиться к нулевому адресу вот тут:

procedure TTileDownloaderSimple.OnConfigChange;
var
  VTileDownloaderConfigStatic: ITileDownloaderConfigStatic;
begin
  VTileDownloaderConfigStatic := FTileDownloaderConfig.GetStatic; // <- AV
  ...

в указанной строчке переменная FTileDownloaderConfig оказывается не инициализирована, да и весь Self равен nil.
Видимо, баг в методе нотифаера TNotifierBase.Notify, т.к. он в локе читает список листнеров, а потом вне лока вызывает их. Но может быть ситуация (и это наш случай), когда первый листнер, который получил сообщение (в нашем случае это TTileDownloaderList), берёт и удаляет объекты остальных листнеров этого же нотифаера (TTileDownloaderSimple). И даже не смотря на то, что удаляемые объекты отписываются от нотификаций, им эти нотификации всё равно прилетают, после уничтожения.

procedure TNotifierBase.Notify(const AMsg: IInterface);
var
  idx: Integer;
  VList: array of IListener;
begin
  FSync.BeginRead;
  try
    SetLength(VList, FListeners.Count);
    for idx := 0 to FListeners.Count - 1 do begin
      VList[idx] := IListener(Pointer(FListeners[idx]));
    end;
  finally
    FSync.EndRead;
  end;
  for idx := 0 to Length(VList) - 1 do begin
    VList[idx].Notification(AMsg);
    VList[idx] := nil;
  end;
  VList := nil;
end;
No tags attached.
Issue History
19-05-2016 10:10zedNew Issue
19-05-2016 10:16zedNote Added: 0017218
19-05-2016 10:19vdemidovNote Added: 0017219
19-05-2016 10:21vdemidovNote Added: 0017220
19-05-2016 10:23vdemidovNote Added: 0017221
19-05-2016 10:38zedNote Added: 0017222
19-05-2016 10:55vdemidovNote Added: 0017223
19-05-2016 14:39zedStatusnew => resolved
19-05-2016 14:39zedFixed in Version => 160606
19-05-2016 14:39zedResolutionopen => fixed
19-05-2016 14:39zedAssigned To => zed
19-05-2016 14:39zedTarget Version => 160606

Notes
(0017218)
zed   
19-05-2016 10:16   
Думаю впереть вот такую конструкцию для фикса AV конкретно в TTileDownloaderSimple:

procedure TTileDownloaderSimple.OnConfigChange;
var
  VTileDownloaderConfigStatic: ITileDownloaderConfigStatic;
begin
  if not Assigned(FTileDownloaderConfig) then begin
    {$MESSAGE HINT 'http://www.sasgis.org/mantis/view.php?id=3029'}
    Exit;
  end;
  VTileDownloaderConfigStatic := FTileDownloaderConfig.GetStatic;
  ...

Или есть лучший вариант?
(0017219)
vdemidov   
19-05-2016 10:19   
Да, есть такая проблема. И я не знаю как ее решить. Переделка TNotifierBase.Notify с запихиванием нотификации под лок, только добавит возможность дедлока.
(0017220)
vdemidov   
19-05-2016 10:21   
> Думаю впереть вот такую конструкцию для фикса AV конкретно в TTileDownloaderSimple
Боюсь это проблему, только сделает чуть-чуть более редкой, но не решит.
(0017221)
vdemidov   
19-05-2016 10:23   
Как вариант убрать из TTileDownloaderSimple слежение за конфигом, а следить за ним на уровень выше и при изменении пере создавать весь массив.
(0017222)
zed   
19-05-2016 10:38   
Т.е. в TTileDownloaderList.OnConfigChange выкинуть всю ту логику с копированием, а в TTileDownloaderSimple в качестве параметра передавать ITileDownloaderConfigStatic? Можно попробовать. Главное, чтобы в будущем ни у кого не возникло желания сделать оптимизацию и перенести часть слежения обратно в TTileDownloaderSimple. Придётся писать предупреждение от такого.
(0017223)
vdemidov   
19-05-2016 10:55   
>Т.е. в TTileDownloaderList.OnConfigChange выкинуть всю ту логику с копированием, а в TTileDownloaderSimple в качестве параметра передавать ITileDownloaderConfigStatic?

Ага.

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