Jump to content
ertank

Segmentation fault while trying to receive UDP broadcast messages on Android

Recommended Posts

Hello,

 

I am using Delphi 10.3.3, Indy 10.6.2.5366 (stock Indy version)

 

I am trying to listen broadcast UDP messages on Android (exact version I am testing is 4.4.2, I need to support latest versions, too) connected to a local WiFi. Broadcast messages will be sent in same network.

 

I have found this page https://stackoverflow.com/questions/19040674/delphi-xe5-tidudpserver-does-not-receive-anything-on-android and tried to do it as to my understanding.

 

Project has CHANGE_WIFI_MULTICAST_STATE permission set.

 

UDP server parameters are as following at design time and everything else is left as default:

TIdUDPServer.Active = False

TIdUDPServer.BroadcastEnable = True

TIdUDPServer.DefaultPort = 8080

 

I have following code where timer is activated as last line in OnFormCreate() event and has 0.5 seconds delay:

procedure TForm3.tmrCheckPhoneServiceTimer(Sender: TObject);
var
  WifiManagerObj: JObject;
begin
  // Run just once
  tmrCheckPhoneService.Enabled := False;

  // Get necessary OS settings to receive broadcast messages
  Log('Getting multicast lock...');
  WifiManagerObj := TAndroidHelper.Context.getSystemService(TJContext.JavaClass.WIFI_SERVICE);
  FWifiManager := TJWifiManager.Wrap((WifiManagerObj as ILocalObject).GetObjectID);
  FMulticastLock := FWifiManager.createMulticastLock(StringToJString('LightFactory Remote'));
  FMulticastLock.setReferenceCounted(True);
  FMulticastLock.acquire();
  // Try to open a port on Android by sending a broadcast message
  Log('Sending a broadcast message...');
  IdUDPServer1.Broadcast('test', IdUDPServer1.DefaultPort);
  // Now we can start to listen
  Log('Activating UDP listener...');
  IdUDPServer1.Active := True;

  if FPhoneDialerService = nil then
  begin
    TDialogService.ShowMessage('PhoneDialer service not supported');
    Exit();
  end;

  PermissionsService.RequestPermissions([FCallPhonePermission], MakePhoneCallPermissionRequestResult, DisplayRationale);
end;

I have following code in my OnDestroy() event:

procedure TForm3.FormDestroy(Sender: TObject);
begin
  // Stop listenning.
  IdUDPServer1.Active := False;
  // Release multicast lock
  FMulticastLock.release();
end;

 

My broadcast listening code is as following:

procedure TForm3.IdUDPServer1UDPRead(AThread: TIdUDPListenerThread;
  const AData: TIdBytes; ABinding: TIdSocketHandle);
var
  Data: string;
begin
  Data := TEncoding.Default.GetString(AData);
  TThread.Queue(nil, procedure begin Log('Incoming broadcast message from: ' + ABinding.PeerIP) end);

  if SameText(Data, 'mytriggerphrase') then
  begin
    TThread.Queue(nil, procedure begin TestConnection(ABinding.PeerIP) end);
  end;
end;

 

After I run the app, I have following in my log:

2020-01-14 00:59:33.697 Getting multicast lock...
2020-01-14 00:59:33.736 Sending a broadcast message...
2020-01-14 00:59:33.740 Activating UDP listener...
2020-01-14 00:59:33.804 Incoming broadcast message from: 192.168.1.186

That IP number in above log belongs to Android device itself.

 

I have following problems that I could not figure a solution:

1- I do not get any other broadcast message from my PC application which is sending one broadcast message each second. Is there anything I am doing wrong for that to happen?

2- I get segmentation fault (11) when closing my app. Detailed call stack and exact line is in attached picture. This happens each and every close. I did not understand why.

 

Lastly, I am not sure if my code is doing it correct to keep multicast lock thru all run-time. I read a suggestion to release a multicast lock once finished with it in order to save battery life. So, I wonder if I can release multicast lock right after enabling my TIdUDPServer?

 

Any help is appreciated.

 

Thanks & regards,

Ertan

 

image.png

Edited by ertank
Typing fix
  • Like 1

Share this post


Link to post

As far as the MulticastLock goes, you should call acquire when your app needs to listen (i.e. when your TIdUPDServer is active), and release when it does not.

 

That said, I've given up on Indy for multicast because there have been too many hoops to jump through, including for Android. 

 

 

Share this post


Link to post

I can't really comment on the multicast issue, especially on Android.  But if you are really doing multicasting, why are you using TIdUDPServer?  That is not a multicast-enabled component.  Indy has separate TIdIPMCastClient and TIdIPMCastServer components specifically for multicast.

 

For UDP, assuming a plain vanilla network broadcast is in fact, being used, are you setting up the TIdUDPServer.Bindings collection at all prior to activating the server?

 

One problem I do see is your use of TThread.Queue() captures the ABinding object, which is no longer valid once the TIdUDPServer is freed.  Delphi anonymous procedures capture variables, not values.  You should use TThread.Queue() more like this instead:

procedure TForm3.QueueLog(const AMsg: string);
begin
  TThread.Queue(nil,
    procedure
    begin
      Log(AMsg);
    end
  );
end;

procedure TForm3.QueueTestConnection(const APeerIP: string);
begin
  TThread.Queue(nil,
    procedure
    begin
      TestConnection(APeerIP);
    end
  );
end;

procedure TForm3.IdUDPServer1UDPRead(AThread: TIdUDPListenerThread;
  const AData: TIdBytes; ABinding: TIdSocketHandle);
var
  Data: string;
begin
  Data := TEncoding.Default.GetString(AData);
  QueueLog('Incoming broadcast message from: ' + ABinding.PeerIP);

  if SameText(Data, 'mytriggerphrase') then
  begin
    QueueTestConnection(ABinding.PeerIP);
  end;
end;

 

Edited by Remy Lebeau
  • Like 2

Share this post


Link to post
23 hours ago, Remy Lebeau said:

I can't really comment on the multicast issue, especially on Android.  But if you are really doing multicasting, why are you using TIdUDPServer?  That is not a multicast-enabled component.  Indy has separate TIdIPMCastClient and TIdIPMCastServer components specifically for multicast.

I did not know until I read your reply that there are dedicated components. I am going to try them.

23 hours ago, Remy Lebeau said:

For UDP, assuming a plain vanilla network broadcast is in fact, being used, are you setting up the TIdUDPServer.Bindings collection at all prior to activating the server?

Below is all I set on TIdUDPServer. Actually, I set these at design time and activate component at run-time as in my initial post.

TIdUDPServer.Active := False;
TIdUDPServer.BroadcastEnable := True;
TIdUDPServer.DefaultPort := 8080;

 

23 hours ago, Remy Lebeau said:

One problem I do see is your use of TThread.Queue() captures the ABinding object, which is no longer valid once the TIdUDPServer is freed.  Delphi anonymous procedures capture variables, not values.  You should use TThread.Queue() more like this instead:

Using suggested code, I still get segmentation fault (11) when app exists. However, call stack changed a little this time as in attached screen shot.

image.thumb.png.cdf3695dd5f1738991ed7e10072b6837.png

Share this post


Link to post
1 hour ago, ertank said:

Using suggested code, I still get segmentation fault (11) when app exists. However, call stack changed a little this time

Do you have the same error if you manually deactivate the TIdUDPServer BEFORE exiting your app? That would allow Indy to clean up everything in a more deterministic manner.  The TIdUDPServer destructor will normally deactivate the server and clean up, but there is no call to that destructor in your stack trace.  The stack trace suggests the RTL is freeing a UDP listener thread that is still actively running, but crashes when the TIdThread destructor tries to decrement the global IdThread.GThreadCount counter, which has probably already been finalized and freed by the RTL before the listener thread is freed.  So you should deactivate the server beforehand to ensure there are no threads running when the RTL does its own cleanup.

Edited by Remy Lebeau

Share this post


Link to post
On 1/14/2020 at 11:06 AM, Dave Nottage said:

As far as the MulticastLock goes, you should call acquire when your app needs to listen (i.e. when your TIdUPDServer is active), and release when it does not.

Incidentally, I suspect that leaving it "acquired" can consume a lot of battery (due to what happened to my device), so it might pay to check when the screen locks and do a release.

Share this post


Link to post
On 1/16/2020 at 12:28 AM, Remy Lebeau said:

Do you have the same error if you manually deactivate the TIdUDPServer BEFORE exiting your app? That would allow Indy to clean up everything in a more deterministic manner.  The TIdUDPServer destructor will normally deactivate the server and clean up, but there is no call to that destructor in your stack trace.  The stack trace suggests the RTL is freeing a UDP listener thread that is still actively running, but crashes when the TIdThread destructor tries to decrement the global IdThread.GThreadCount counter, which has probably already been finalized and freed by the RTL before the listener thread is freed.  So you should deactivate the server beforehand to ensure there are no threads running when the RTL does its own cleanup.

So far I have tried following on a fresh project using one TIdTCPServer and one TIdIPMCastServer:

- Set Active property to False any Indy component on form manually way before closing app: Failed

- Set Active property to False any Indy component on form manually. Free them way before closing app: Failed

- Remove all auto-create components. Create them using code without owner. Set Active property of them to False way before closing app: Failed

- Remove all auto-create components. Create them using code without owner. Set Active property of them to False. Free them at run-time way before closing app: Failed

- Remove all auto-create components. Create them using code without owner. Set Active property of them to False. *Do not* Free them at run-time and close app: Failed (this one was just for my curiosity)

 

I get very similar call stack for all tests above.

 

Then I tried to do some tests using just a single component:

- Using run-time created TIdTCPServer with no owner. Active property set to False at run-time. *Did not* free and closing app: No exception (just some memory leak obviously)

- Using run-time created TIdTCPServer with no owner. Active property set to False at run-time. Freed before closing app: No exception

- Using run-time created TIdTCPServer with no owner. Active property *not* set to False at run-time. Freed before closing app: No exception

- Using design-time placed TIdTCPServer with no owner. Active property set to False at run-time way before closing app: Failed 

- Using run-time created TIdIPMCastServer with no owner. Active property set to True, used TIdIPMCastServer.Send() and property set to False. Freed at run-time way before closing app: No exception

 

It is interesting that using either component alone created at run-time is not failing at all. Mixing them both seems to be having a problem. Using TIdUDPClient for sending broadcast messages with TIdTCPServer also failing.

 

In anyway, it seems to be best creating components at run-time as putting them on form at design time is always failing.

Share this post


Link to post
On 1/18/2020 at 2:56 PM, ertank said:

So far I have tried following on a fresh project using one TIdTCPServer and one TIdIPMCastServer:

- Set Active property to False any Indy component on form manually way before closing app: Failed

- Set Active property to False any Indy component on form manually. Free them way before closing app: Failed

- Remove all auto-create components. Create them using code without owner. Set Active property of them to False way before closing app: Failed

- Remove all auto-create components. Create them using code without owner. Set Active property of them to False. Free them at run-time way before closing app: Failed

- Remove all auto-create components. Create them using code without owner. Set Active property of them to False. *Do not* Free them at run-time and close app: Failed (this one was just for my curiosity)

Failed HOW exactly?  Please be more specific.

On 1/18/2020 at 2:56 PM, ertank said:

I get very similar call stack for all tests above.

I would need to see the actual call stacks.  TIdTCPServer is a very different component than TIdUDPServer, which is a very component from TIdIPMCastServer.

On 1/18/2020 at 2:56 PM, ertank said:

It is interesting that using either component alone created at run-time is not failing at all. Mixing them both seems to be having a problem. Using TIdUDPClient for sending broadcast messages with TIdTCPServer also failing.

Again, I need SPECIFIC DETAILS, or I can't help you further.

On 1/18/2020 at 2:56 PM, ertank said:

In anyway, it seems to be best creating components at run-time as putting them on form at design time is always failing.

There should be no difference.  Components created at design-time are created dynamically at runtime, just with the parent Form/Frame/DataModule as the Owner.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×