Recent

Author Topic: App crashes in main menu after using NSURLSessions - macOS 15  (Read 881 times)

Igor Kokarev

  • Sr. Member
  • ****
  • Posts: 382
App crashes in main menu after using NSURLSessions - macOS 15
« on: October 02, 2024, 01:51:04 pm »
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  [Select][+][-]
  1. An unhandled exception occurred at $0000000193920DA4:
  2. EAccessViolation: Access violation
  3.   $0000000193920DA4
  4.   $0000000191A9AF78
  5.   $0000000191A7A0BC
  6.   $0000000191A70110
  7.   $0000000191A6FDBC
« Last Edit: October 02, 2024, 01:57:06 pm by Igor Kokarev »

Igor Kokarev

  • Sr. Member
  • ****
  • Posts: 382
Re: App crashes in main menu after using NSURLSessions - macOS 15
« Reply #1 on: October 02, 2024, 01:54:23 pm »
The code of my simple test app:

Code: Pascal  [Select][+][-]
  1. program SimpleApp;
  2.  
  3. {$mode objfpc}
  4. {$modeswitch objectivec1}
  5.  
  6. uses
  7.   cthreads,
  8.   CocoaAll, MacOSAll, ns_session;
  9.  
  10. type
  11.   TMyWndDelegate = objcclass(NSObject, NSWindowDelegateProtocol)
  12.     function windowShouldClose (sender: id): ObjCBOOL; message 'windowShouldClose:';
  13.   end;
  14.  
  15. var
  16.   App:    NSApplication;
  17.   Wnd:    NSWindow;
  18.   wDeleg: TMyWndDelegate;
  19.  
  20. var
  21.   SubTypeTest: Integer=669;
  22.  
  23. function TMyWndDelegate.windowShouldClose (sender: id): ObjCBOOL;
  24. begin
  25.   Result:=True;
  26.   App.terminate(sender);
  27. end;
  28.  
  29. procedure DownloadFile;
  30. var
  31.   Srv: TNSHTTPSendAndReceive;
  32. const
  33.   url = 'https://wiki.freepascal.org/Main_Page';
  34. begin
  35.   Srv:=TNSHTTPSendAndReceive.Create;
  36.   Srv.URlReq(url);
  37.   Srv.Free;
  38. end;
  39.  
  40. function Loop(P: Pointer): PtrInt;
  41. begin
  42.   Result:=0;
  43.   while App.isRunning do begin
  44.     inc(SubTypeTest);
  45.     NSThread.sleepForTimeInterval(1);
  46.     if SubTypeTest=670 then DownloadFile;
  47.   end;
  48. end;
  49.  
  50. begin
  51.   App:=NSApplication.sharedapplication;
  52.   App.finishLaunching;
  53.  
  54.   Wnd:=NSWindow.alloc.initWithContentRect_styleMask_backing_defer(NSMakeRect(0, 0, 10, 10),
  55.       NSTitledWindowMask or NSClosableWindowMask or NSMiniaturizableWindowMask or NSResizableWindowMask,
  56.       NSBackingStoreBuffered, False);
  57.   wDeleg:=TMyWndDelegate.alloc.init;
  58.   Wnd.setDelegate(wDeleg);
  59.   Wnd.setFrame_display(NSMakeRect(200,200,400,300), True);
  60.   Wnd.makeKeyandOrderFront(nil);
  61.   beginThread(@Loop);
  62.   App.run;
  63.   EndThread;
  64. end.
  65.  

Code: Pascal  [Select][+][-]
  1. unit ns_session;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$modeswitch objectivec1}
  5. {$modeswitch cblocks}
  6.  
  7. interface
  8.  
  9. uses
  10.   SysUtils,   // for IntToStr()
  11.   MacOSAll,
  12.   CocoaAll;   // for NSData and other Cocoa types
  13.  
  14. type
  15.   // setup cBlock for completion handler
  16.   tblock = reference to procedure(data: NSData; response: NSURLResponse; connectionError: NSError); cdecl; cblock;
  17.  
  18.   // redefine version from packages/cocoaint/src/foundation/NSURLSession.inc
  19.   NSURLSession = objcclass external (NSObject)
  20.   public
  21.     class function sessionWithConfiguration(configuration: NSURLSessionConfiguration): NSURLSession; message 'sessionWithConfiguration:';
  22.   end;
  23.  
  24.   NSURLSessionAsynchronousConvenience = objccategory external (NSURLSession)
  25.     function dataTaskWithURL_completionHandler(url: NSURL; completionHandler: tBlock): NSURLSessionDataTask; message 'dataTaskWithURL:completionHandler:';
  26.   end;
  27.  
  28.   { TNSHTTPSendAndReceive }
  29.   TNSHTTPSendAndReceive = class(TObject)
  30.   private
  31.     procedure myCompletionHandler(data: NSData; response: NSURLResponse; connectionError: NSError);
  32.   public
  33.     procedure URlReq(Address: AnsiString);
  34.   end;
  35.  
  36. var
  37.   myCache: NSURLcache;
  38.   webData: String;
  39.   webResponse: String;
  40.   webHTML: String;
  41.   webStatusCode: Integer;
  42.   webError: String;
  43.   webErrorReason: String;
  44.   didFinish: PRTLEvent;
  45.  
  46. implementation
  47.  
  48. function CFStrToAnsiStr(cfStr    : CFStringRef;
  49.                         encoding : CFStringEncoding = kCFStringEncodingWindowsLatin1): AnsiString;
  50.  {Convert CFString to AnsiString.
  51.   If encoding is not specified, encode using CP1252.}
  52. var
  53.   StrPtr   : Pointer;
  54.   StrRange : CFRange;
  55.   StrSize  : CFIndex;
  56. begin
  57.   if cfStr = nil then
  58.     begin
  59.     Result := '';
  60.     Exit;
  61.     end;
  62.  
  63.    {First try the optimized function}
  64.   StrPtr := CFStringGetCStringPtr(cfStr, encoding);
  65.   if StrPtr <> nil then  {Succeeded?}
  66.     Result := PChar(StrPtr)
  67.   else  {Use slower approach - see comments in CFString.pas}
  68.     begin
  69.     StrRange.location := 0;
  70.     StrRange.length := CFStringGetLength(cfStr);
  71.  
  72.      {Determine how long resulting string will be}
  73.     CFStringGetBytes(cfStr, StrRange, encoding, Ord('?'),
  74.                      False, nil, 0, StrSize);
  75.     SetLength(Result, StrSize);  {Expand string to needed length}
  76.  
  77.     if StrSize > 0 then  {Convert string?}
  78.       CFStringGetBytes(cfStr, StrRange, encoding, Ord('?'),
  79.                        False, @Result[1], StrSize, StrSize);
  80.     end;
  81. end;  {CFStrToAnsiStr}
  82.  
  83. function NSStrToStr(aNSStr   : NSString;
  84.                     encoding : CFStringEncoding = kCFStringEncodingWindowsLatin1): string;
  85.  {Convert NSString to string.
  86.   If encoding is not specified, encode using CP1252.
  87.   This assumes string = AnsiString.}
  88. begin
  89.    {Note NSString and CFStringRef are interchangable}
  90.   Result := CFStrToAnsiStr(CFStringRef(aNSStr), encoding);
  91. end;
  92.  
  93. // Completion handler: Executed after URL has been retrieved or retrieval fails
  94. procedure TNSHTTPSendAndReceive.myCompletionHandler(data: NSData; response: NSURLResponse; connectionError: NSError);
  95. var
  96.   httpResponse: NSHTTPURLResponse;
  97. begin
  98.   {$IFDEF DEBUG}
  99.   NSLog(NSStr('Completion handler called'));
  100.  
  101.   if (NSThread.currentThread.isMainThread) then
  102.      NSLog(NSStr('In main thread--completion handler'))
  103.   else
  104.      NSLog(NSStr('Not in main thread--completion handler'));
  105.   {$ENDIF}
  106.  
  107.   // if no error
  108.   if((data.Length > 0) and (connectionError = Nil)) then
  109.     begin
  110.       {$IFDEF DEBUG}
  111.       NSLog(NSStr('Data desciption: %@'),data.description);
  112.       NSLog(NSStr('Response description: %@'),response.description);
  113.       NSLog(NSStr('Data: %@'),NSString.alloc.initWithData_encoding(data,NSASCIIStringEncoding).autorelease);
  114.       {$ENDIF}
  115.       webData     :=  NSStrToStr(data.description);
  116.       {$IFDEF DEBUG}
  117.       NSLog(NSStr('Web data' + webData));
  118.       {$ENDIF}
  119.       // The NSHTTPURLResponse class is a subclass of NSURLResponse
  120.       // so we can cast an NSURLResponse as an NSHTTPURLResponse
  121.       httpResponse := NSHTTPURLResponse(response);
  122.       {$IFDEF DEBUG}
  123.       // Extract status code from response header
  124.       NSLog(NSStr('HTTP status code: %@'),NSStr(IntToStr(httpResponse.statusCode)));
  125.       {$ENDIF}
  126.       webStatusCode :=  httpResponse.statusCode;
  127.       webResponse   :=  NSStrToStr(response.description);
  128.       webHTML       :=  NSStrToStr(NSString.alloc.initWithData_encoding(data,NSASCIIStringEncoding));
  129.     end
  130.   // o/w return error
  131.   else
  132.     begin
  133.       {$IFDEF DEBUG}
  134.       NSLog(NSStr('Error %@'), connectionError.userInfo);
  135.       {$ENDIF}
  136.       webError := 'Error description: ' + LineEnding +  NSStrToStr(connectionError.description);
  137.       webErrorReason := 'Error retrieving: ' + NSStrToStr(connectionError.userInfo.valueForKey(NSErrorFailingUrlStringKey))
  138.         + LineEnding + LineEnding + 'Reason: ' + NSStrToStr(connectionError.localizedDescription);
  139.     end;
  140.  
  141.   // notify main thread that completion handler thread has finished
  142.   RTLEventSetEvent(didFinish);
  143.  
  144.   {$IFDEF DEBUG}
  145.   NSLog(NSStr('leaving handler'));
  146.   {$ENDIF}
  147. end;
  148.  
  149. procedure TNSHTTPSendAndReceive.URlReq(Address: AnsiString);
  150. var
  151.   urlSessionConfig: NSURLSessionConfiguration = Nil;
  152.   cachePath: NSString = Nil;
  153.   urlSession: NSURLSession = Nil;
  154.   URL: NSURL = Nil;
  155. begin
  156.   // create event to synchronise completion handler background thread
  157.   // with the GUI main thread
  158.   didFinish := RTLEventCreate;
  159.  
  160.   // create default url session config
  161.   urlSessionConfig := NSURLSessionConfiguration.defaultSessionConfiguration;
  162.  
  163.   // configure caching behavior for the default session
  164.   cachePath := NSTemporaryDirectory.stringByAppendingPathComponent(NSStr('/nsurlsessiondemo.cache'));
  165.  
  166.   {$IFDEF DEBUG}
  167.   NSLog(NSStr('Cache path: %@'), cachePath);
  168.   {$ENDIF}
  169.  
  170.   myCache := NSURLCache.alloc.initWithMemoryCapacity_diskCapacity_diskPath(16384, 268435456, cachePath);
  171.   urlSessionConfig.setURLCache(myCache);
  172.  
  173.   // set cache policy
  174.   {$IFDEF DEBUG}
  175.   urlSessionConfig.setRequestCachePolicy(NSURLRequestReloadIgnoringLocalCacheData);
  176.   {$ELSE}
  177.   urlSessionConfig.setRequestCachePolicy(NSURLRequestUseProtocolCachePolicy);
  178.   {$ENDIF}
  179.  
  180.   // create a session for configuration
  181.   urlSession := NSURLSession.sessionWithConfiguration(urlSessionConfig);
  182.  
  183.   // create NSURL
  184.   URL := NSURL.URLWithString(NSSTR(Address));
  185.   if(Url = Nil) then
  186.       writeln('NSURL.URLWithString failed!');
  187.  
  188.   // setup and execute data task
  189.   urlSession.dataTaskWithURL_completionHandler(URL, @myCompletionHandler).resume;
  190.  
  191.   // wait for completion handler to finish
  192.   RTLEventWaitFor(didFinish);
  193.  
  194.   // display results
  195.   if(webErrorReason <> '') then
  196.     begin
  197.       writeln(webError);
  198.       writeln(webErrorReason);
  199.     end
  200.   else
  201.     begin
  202.       writeln('HTTP status code: ' + IntToStr(webStatusCode) + LineEnding
  203.         + LineEnding + 'Raw data: ' + LineEnding + webData);
  204.       writeln('Response: ' + LineEnding + LineEnding + webResponse);
  205.       writeln('Web page: ' + LineEnding + LineEnding + webHTML);
  206.     end;
  207.  
  208.   // housekeeping
  209.   RTLeventdestroy(didFinish);
  210.   myCache.release;
  211. end;
  212.  
  213. end.
  214.  

Igor Kokarev

  • Sr. Member
  • ****
  • Posts: 382
Re: App crashes in main menu after using NSURLSessions - macOS 15
« Reply #2 on: October 08, 2024, 02:37:31 pm »
I think it can be some bug in the FPC compiler. Because the problem occurs with a very simple test code.

Igor Kokarev

  • Sr. Member
  • ****
  • Posts: 382
Re: App crashes in main menu after using NSURLSessions - macOS 15
« Reply #3 on: March 16, 2025, 08:02:20 am »
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  [Select][+][-]
  1. tblock = reference to procedure(...); cdecl; cblock;

and then call:

Code: Pascal  [Select][+][-]
  1. 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  [Select][+][-]
  1. var
  2.   safeBlock: tblock;
  3. ...
  4. safeBlock := Block_copy(@myCompletionHandler);
  5. 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."
« Last Edit: March 16, 2025, 08:05:35 am by Igor Kokarev »

Igor Kokarev

  • Sr. Member
  • ****
  • Posts: 382
Re: App crashes in main menu after using NSURLSessions - macOS 15
« Reply #4 on: March 19, 2025, 02:18:23 pm »
I tried with FPC 3.2.X fixes tree and also with Trunk version (March 19). The problem is not solved.

 

TinyPortal © 2005-2018