Forum > Cocoa

App crashes in main menu after using NSURLSessions - macOS 15

(1/2) > >>

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

Go to full version