From e387588503c628b9267c18ea4cb884e4777fa02d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 25 Jun 2026 12:27:00 +0200 Subject: [PATCH] fix(developer): warn only on race when destroying TAppSourceHttpResponder I uncovered one race, documented in the source. Not trying to resolve that race at this time (it is not consequential). There is a second condition which is unclear -- and may have other consequences. So report a warning to Sentry when this arises, but do not crash out on the user. Fixes: #11916 Test-bot: skip --- ....Developer.System.HttpServer.AppSource.pas | 19 +++++++++++++++++-- developer/src/tike/main/UframeTextEditor.pas | 7 ++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/developer/src/tike/http/Keyman.Developer.System.HttpServer.AppSource.pas b/developer/src/tike/http/Keyman.Developer.System.HttpServer.AppSource.pas index 904d389a089..5c125aa4cd4 100644 --- a/developer/src/tike/http/Keyman.Developer.System.HttpServer.AppSource.pas +++ b/developer/src/tike/http/Keyman.Developer.System.HttpServer.AppSource.pas @@ -79,8 +79,23 @@ destructor TAppSourceHttpResponder.Destroy; // Note: Unlike regular functions, Assert has short-circuit evaluation // intrinsics on the first param which makes it safe to dereference T[0] in // the second parameter. - Assert(T.Count = 0, 'TAppSourceHttpResponder.Sources should be empty at destruction '+ - '(T.Count='+IntToStr(T.Count)+', T[0].Filename='+T[0].Filename+')'); + + // There is a race where RegisterSource is called on the server side + // where a request is started in the form but the server does not respond + // before the form is destroyed: + // 1. http request starts on form + // 2. Form destroyed, calls UnregisterSource + // 3. http request received in TAppSourceHttpResponder, + // RespondTouchEditorState calls RegisterSource + // 4. Ooops + + // There is another race somewhere with unsaved text editors, or else a + // resource leak. For now, we'll report this as a message rather than crash. + if T.Count > 0 then + begin + TKeymanSentryClient.Instance.ReportMessage('TAppSourceHttpResponder.Sources should be empty at destruction '+ + '(T.Count='+IntToStr(T.Count)+', T[0].Filename='+T[0].Filename+')', True); + end; finally FSources.UnlockList; end; diff --git a/developer/src/tike/main/UframeTextEditor.pas b/developer/src/tike/main/UframeTextEditor.pas index de6e2937749..b9f5f8ce227 100644 --- a/developer/src/tike/main/UframeTextEditor.pas +++ b/developer/src/tike/main/UframeTextEditor.pas @@ -486,7 +486,12 @@ procedure TframeTextEditor.LoadFileInBrowser(const AData: string); function GenerateNewFilename: string; begin Inc(FInitialFilenameIndex); - Result := '*texteditor*'+IntToStr(FInitialFilenameIndex); + Result := '*texteditor'; + if Owner <> nil then + Result := Result + '*' + Owner.ClassName; + if Parent <> nil then + Result := Result + '*' + Parent.Name; + Result := Result + '*'+IntToStr(FInitialFilenameIndex); end; function EncodeFont(const prefix: string; f: TFont): string; begin