Recent

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

Igor Kokarev

  • Sr. Member
  • ****
  • Posts: 375
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: 375
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: 375
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.

 

TinyPortal © 2005-2018