Forum > Cocoa
App crashes in main menu after using NSURLSessions - macOS 15
Igor:
My app crashes when I click on any item in the main menu IF I previously used the NSURLSessions framework to download a file.
And this issue only occurs on macOS 15 and on Apple Silicon (M1/M2/M3) in aarch64 mode.
FPC 3.2.2
I recreated this issue in a simple test app where I used only pure FPC RTL units, no LCL. I used NSURLSessions example code from this page:
https://wiki.lazarus.freepascal.org/macOS_NSURLSession
I attach my simple app code below.
1. Compile and run the test app.
2. Make sure that app's window is focused. Click Apple logo in the main menu at the top-left. The app will crash.
In my real app it crashes when I click to any main menu item to see a sub-menu.
I tried to modify my real app. If firstly I click to the main menu items and THEN use NSURLSessions API, main menu will work correctly.
This issue doesn't occur with a deprecated NSURLConnection framework.
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---An unhandled exception occurred at $0000000193920DA4:EAccessViolation: Access violation $0000000193920DA4 $0000000191A9AF78 $0000000191A7A0BC $0000000191A70110 $0000000191A6FDBC
Igor:
The code of my simple test app:
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---program SimpleApp; {$mode objfpc}{$modeswitch objectivec1} uses cthreads, CocoaAll, MacOSAll, ns_session; type TMyWndDelegate = objcclass(NSObject, NSWindowDelegateProtocol) function windowShouldClose (sender: id): ObjCBOOL; message 'windowShouldClose:'; end; var App: NSApplication; Wnd: NSWindow; wDeleg: TMyWndDelegate; var SubTypeTest: Integer=669; function TMyWndDelegate.windowShouldClose (sender: id): ObjCBOOL;begin Result:=True; App.terminate(sender);end; procedure DownloadFile;var Srv: TNSHTTPSendAndReceive;const url = 'https://wiki.freepascal.org/Main_Page';begin Srv:=TNSHTTPSendAndReceive.Create; Srv.URlReq(url); Srv.Free;end; function Loop(P: Pointer): PtrInt;begin Result:=0; while App.isRunning do begin inc(SubTypeTest); NSThread.sleepForTimeInterval(1); if SubTypeTest=670 then DownloadFile; end;end; begin App:=NSApplication.sharedapplication; App.finishLaunching; Wnd:=NSWindow.alloc.initWithContentRect_styleMask_backing_defer(NSMakeRect(0, 0, 10, 10), NSTitledWindowMask or NSClosableWindowMask or NSMiniaturizableWindowMask or NSResizableWindowMask, NSBackingStoreBuffered, False); wDeleg:=TMyWndDelegate.alloc.init; Wnd.setDelegate(wDeleg); Wnd.setFrame_display(NSMakeRect(200,200,400,300), True); Wnd.makeKeyandOrderFront(nil); beginThread(@Loop); App.run; EndThread;end.
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---unit ns_session; {$mode objfpc}{$H+}{$modeswitch objectivec1}{$modeswitch cblocks} interface uses SysUtils, // for IntToStr() MacOSAll, CocoaAll; // for NSData and other Cocoa types type // setup cBlock for completion handler tblock = reference to procedure(data: NSData; response: NSURLResponse; connectionError: NSError); cdecl; cblock; // redefine version from packages/cocoaint/src/foundation/NSURLSession.inc NSURLSession = objcclass external (NSObject) public class function sessionWithConfiguration(configuration: NSURLSessionConfiguration): NSURLSession; message 'sessionWithConfiguration:'; end; NSURLSessionAsynchronousConvenience = objccategory external (NSURLSession) function dataTaskWithURL_completionHandler(url: NSURL; completionHandler: tBlock): NSURLSessionDataTask; message 'dataTaskWithURL:completionHandler:'; end; { TNSHTTPSendAndReceive } TNSHTTPSendAndReceive = class(TObject) private procedure myCompletionHandler(data: NSData; response: NSURLResponse; connectionError: NSError); public procedure URlReq(Address: AnsiString); end; var myCache: NSURLcache; webData: String; webResponse: String; webHTML: String; webStatusCode: Integer; webError: String; webErrorReason: String; didFinish: PRTLEvent; implementation function CFStrToAnsiStr(cfStr : CFStringRef; encoding : CFStringEncoding = kCFStringEncodingWindowsLatin1): AnsiString; {Convert CFString to AnsiString. If encoding is not specified, encode using CP1252.}var StrPtr : Pointer; StrRange : CFRange; StrSize : CFIndex;begin if cfStr = nil then begin Result := ''; Exit; end; {First try the optimized function} StrPtr := CFStringGetCStringPtr(cfStr, encoding); if StrPtr <> nil then {Succeeded?} Result := PChar(StrPtr) else {Use slower approach - see comments in CFString.pas} begin StrRange.location := 0; StrRange.length := CFStringGetLength(cfStr); {Determine how long resulting string will be} CFStringGetBytes(cfStr, StrRange, encoding, Ord('?'), False, nil, 0, StrSize); SetLength(Result, StrSize); {Expand string to needed length} if StrSize > 0 then {Convert string?} CFStringGetBytes(cfStr, StrRange, encoding, Ord('?'), False, @Result[1], StrSize, StrSize); end;end; {CFStrToAnsiStr} function NSStrToStr(aNSStr : NSString; encoding : CFStringEncoding = kCFStringEncodingWindowsLatin1): string; {Convert NSString to string. If encoding is not specified, encode using CP1252. This assumes string = AnsiString.}begin {Note NSString and CFStringRef are interchangable} Result := CFStrToAnsiStr(CFStringRef(aNSStr), encoding);end; // Completion handler: Executed after URL has been retrieved or retrieval failsprocedure TNSHTTPSendAndReceive.myCompletionHandler(data: NSData; response: NSURLResponse; connectionError: NSError);var httpResponse: NSHTTPURLResponse;begin {$IFDEF DEBUG} NSLog(NSStr('Completion handler called')); if (NSThread.currentThread.isMainThread) then NSLog(NSStr('In main thread--completion handler')) else NSLog(NSStr('Not in main thread--completion handler')); {$ENDIF} // if no error if((data.Length > 0) and (connectionError = Nil)) then begin {$IFDEF DEBUG} NSLog(NSStr('Data desciption: %@'),data.description); NSLog(NSStr('Response description: %@'),response.description); NSLog(NSStr('Data: %@'),NSString.alloc.initWithData_encoding(data,NSASCIIStringEncoding).autorelease); {$ENDIF} webData := NSStrToStr(data.description); {$IFDEF DEBUG} NSLog(NSStr('Web data' + webData)); {$ENDIF} // The NSHTTPURLResponse class is a subclass of NSURLResponse // so we can cast an NSURLResponse as an NSHTTPURLResponse httpResponse := NSHTTPURLResponse(response); {$IFDEF DEBUG} // Extract status code from response header NSLog(NSStr('HTTP status code: %@'),NSStr(IntToStr(httpResponse.statusCode))); {$ENDIF} webStatusCode := httpResponse.statusCode; webResponse := NSStrToStr(response.description); webHTML := NSStrToStr(NSString.alloc.initWithData_encoding(data,NSASCIIStringEncoding)); end // o/w return error else begin {$IFDEF DEBUG} NSLog(NSStr('Error %@'), connectionError.userInfo); {$ENDIF} webError := 'Error description: ' + LineEnding + NSStrToStr(connectionError.description); webErrorReason := 'Error retrieving: ' + NSStrToStr(connectionError.userInfo.valueForKey(NSErrorFailingUrlStringKey)) + LineEnding + LineEnding + 'Reason: ' + NSStrToStr(connectionError.localizedDescription); end; // notify main thread that completion handler thread has finished RTLEventSetEvent(didFinish); {$IFDEF DEBUG} NSLog(NSStr('leaving handler')); {$ENDIF}end; procedure TNSHTTPSendAndReceive.URlReq(Address: AnsiString);var urlSessionConfig: NSURLSessionConfiguration = Nil; cachePath: NSString = Nil; urlSession: NSURLSession = Nil; URL: NSURL = Nil;begin // create event to synchronise completion handler background thread // with the GUI main thread didFinish := RTLEventCreate; // create default url session config urlSessionConfig := NSURLSessionConfiguration.defaultSessionConfiguration; // configure caching behavior for the default session cachePath := NSTemporaryDirectory.stringByAppendingPathComponent(NSStr('/nsurlsessiondemo.cache')); {$IFDEF DEBUG} NSLog(NSStr('Cache path: %@'), cachePath); {$ENDIF} myCache := NSURLCache.alloc.initWithMemoryCapacity_diskCapacity_diskPath(16384, 268435456, cachePath); urlSessionConfig.setURLCache(myCache); // set cache policy {$IFDEF DEBUG} urlSessionConfig.setRequestCachePolicy(NSURLRequestReloadIgnoringLocalCacheData); {$ELSE} urlSessionConfig.setRequestCachePolicy(NSURLRequestUseProtocolCachePolicy); {$ENDIF} // create a session for configuration urlSession := NSURLSession.sessionWithConfiguration(urlSessionConfig); // create NSURL URL := NSURL.URLWithString(NSSTR(Address)); if(Url = Nil) then writeln('NSURL.URLWithString failed!'); // setup and execute data task urlSession.dataTaskWithURL_completionHandler(URL, @myCompletionHandler).resume; // wait for completion handler to finish RTLEventWaitFor(didFinish); // display results if(webErrorReason <> '') then begin writeln(webError); writeln(webErrorReason); end else begin writeln('HTTP status code: ' + IntToStr(webStatusCode) + LineEnding + LineEnding + 'Raw data: ' + LineEnding + webData); writeln('Response: ' + LineEnding + LineEnding + webResponse); writeln('Web page: ' + LineEnding + LineEnding + webHTML); end; // housekeeping RTLeventdestroy(didFinish); myCache.release;end; end.
Igor:
I think it can be some bug in the FPC compiler. Because the problem occurs with a very simple test code.
Igor:
I asked ChatGPT o1 about this problem (freezing of network code on Apple Silicon running macOS 15 Sequoia.
Should I try FPC Trunk?
"No typos or "missing methods" in your code itself – it's quite correct from an FPC/Cocoa perspective. However, the described crash (when opening the menu after using NSURLSession) on macOS 15 (Sequia) on aarch64 (M1/M2) is apparently not caused by your logic, but by an issue inside Free Pascal on arm64 when working with blocks (i.e., Objective-C blocks) along with NSURLSession.
Reason: a bug in the handling of Objective-C blocks (cblocks) on aarch64 in FPC 3.2.2
In your TNSHTTPSendAndReceive, you use a callback declared as:
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---tblock = reference to procedure(...); cdecl; cblock;
and then call:
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---urlSession.dataTaskWithURL_completionHandler(URL, @myCompletionHandler).resume;
On macOS-ARM64 with FPC 3.2.2/3.2.0, there are known issues (leading to memory crashes) with automatic memory management of such blocks in the FPC runtime, especially if the application continues to interact with Cocoa afterwards (for example, by opening a menu).
As a result, the block is “freed” incorrectly (or freed twice) in the FPC runtime. Thus, on the first interaction with the menu (which triggers Cocoa objects in a certain way), an Access Violation occurs.
That issue typically does not happen:
On Intel macOS (x86_64), due to a different ABI implementation.
If you use NSURLConnection (the old API) – it doesn’t rely on blocks.
If you do things in a different order (e.g., open the menu first, then call NSURLSession) – you might avoid the timing that triggers the crash.
How to work around it
Update/rebuild FPC
In the 3.3.x (trunk) branch, many issues with Objective-C blocks on ARM64 have been partially fixed. These patches did not fully make it into older releases like 3.2.2. Try building your test with the latest FPC builds (3.2.3+, 3.3.1, or future releases).
There is a good chance the issue won’t occur in the newer snapshot versions of FPC.
Avoid blocks (NSURLSession) – use NSURLConnection
Since you say NSURLConnection works, as a temporary workaround, you can continue using it. Although NSURLConnection is deprecated, it may be easier than wrestling with this FPC bug for a smaller or internal app.
Call Block_copy() manually / fix the block’s lifetime
Sometimes it helps to make a manual copy of the Objective-C block before passing it to dataTaskWithURL_completionHandler, so FPC doesn’t free it prematurely. But you need to import Block_copy from libsystemBlocks.dylib yourself and carefully manage it.
For instance, you might do something like:
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---var safeBlock: tblock;...safeBlock := Block_copy(@myCompletionHandler);urlSession.dataTaskWithURL_completionHandler(URL, safeBlock).resume;
(and later call Block_release(safeBlock)), but that requires very careful handling of the block’s lifetime.
Exit the app before using the menu
Obviously not a real solution, but in a test demo you can avoid opening the menu to dodge the crash. In practice, that’s not viable for a real app.
In most real projects, it’s typically easiest either to “fall back to NSURLConnection” or use a newer compiler.
In short
This is not your code’s fault but rather a bug (or incomplete implementation) in FPC 3.2.2 on ARM64 with Objective-C blocks.
You can confirm by testing the exact same code on Intel macOS (x86_64) – it compiles and runs fine without crashing.
The usual solution is using an updated compiler (an unofficial 3.2.3 build or a current 3.3.x trunk) or avoiding NSURLSession (falling back to NSURLConnection).
Official Lazarus/FPC releases where this is “fully” fixed may not be out yet, but in the “nightly” builds or upcoming FPC releases, the relevant fixes are generally included."
Igor:
I tried with FPC 3.2.X fixes tree and also with Trunk version (March 19). The problem is not solved.
Navigation
[0] Message Index
[#] Next page