B4J Question Close Websocket Handler

lip

Active Member
Licensed User
This is a B4J Server app handling lots of Websocket connections.

I am trying to close redundant instances of the WebsocketHandler class.

I maintain a ThreadsafeMap in the Main Activity with an entry in the Map for each Instance of the WebsocketHandler Class. This shows that after a day or so I have maybe five times more instances than I have remote devices. I guess this is devices connecting, then disconnecting, then reconnecting using a new WebsocketHandler instance.

I can see which ones are redundant as the remote device has stopped talking to the Instance. I can also which Instances have a duplicated Session ID. The question is: How do close the redundant instance? I can close the websocket connection (ws.close) and I have tried setting the Instance to Null from the Main Activity but the instance remains active with timers running etc and the number of instances keeps accumulating.

I'm looking for something like "Me.Close" to call from within the Class.
 

LucaMs

Expert
Licensed User
As far as I know you can/should just remove any reference to instances no longer used and then the garbage collection will remove them definitively.
So you have to remove the websocket handler instance from your Map.
 

lip

Active Member
Licensed User
I am using jServer on the Websocket Server.

In AppStart I call srvr.AddWebSocket("/wsc", "CollatorWSH") and I have a Class Module called "CollatorWSH"

Each time a Device connects it creates an Instance of CollatorWSH

I have more Initialized instances of CollatorWSH than I do devices that connect and they seem to accumulate and exists for ever.
 

Jmu5667

Well-Known Member
Licensed User
I am trying to close redundant instances of the WebsocketHandler class.
How do you know you have redundant instances, I suggest using Visual VM and look at the the threads in use. A thread is created for each of the socket connectioned, as I have been told before, once the reference to the socket is no longer in use, it is disposed of and the thread is released. It's the Thread Count that Matters.

In relation to o the thread safe map, untless you are actually doing something with the connections it kind of useless.

And as Erel pointed out, you need to remove it from the map on the socket disconnected event, it won't get removed automatically if that is what you thought.

Are you just running this as a WebServer Application ?
 

lip

Active Member
Licensed User
How do you know you have redundant instances, I suggest using Visual VM and look at the the threads in use. A thread is created for each of the socket connectioned, as I have been told before, once the reference to the socket is no longer in use, it is disposed of and the thread is released. It's the Thread Count that Matters.

In relation to o the thread safe map, untless you are actually doing something with the connections it kind of useless.

And as Erel pointed out, you need to remove it from the map on the socket disconnected event, it won't get removed automatically if that is what you thought.

Are you just running this as a WebServer Application ?
This is a central server (live version running on a cloud-based Ubuntu server and a development version on a Raspberry Pi) that relays messages between Android Tablets and Raspberry Pi's. A message from a Tablet might be sent to multiple Pi's and the responses sent back to the sending Tablet. The thread-safe map in the Main activity allows Connections to send messages to the appropriate Websocket connection based on finding the appropriate Key of the map. Without this I'm not sure how I could identify the Websocket connection?
 

OliverA

Expert
Licensed User
Should I do this?
No. How are you tracking your connections via your ThreadsafeMap? At every connection, are you making sure that the client has connected previously and then updating that clients WebSocket information instead of adding it again? How are you handling time outs - in other words, when and how do you remove WebSocket information from your ThreadsafeMap when a client times out instead of properly logging off/unregistering itself? Show some code.
 

lip

Active Member
Licensed User
How do you know you have redundant instances,?
When the instance is initialised it creates a random InstanceID and starts a Timer. The timer Logs the InstanceID. This shows that the Instance is still running.
 

OliverA

Expert
Licensed User
instance is initialised it creates a random InstanceID
But what do you do with this InstanceID? Do you give this to the client and the client uses this to identify itself? What do you do about WebSocket disconnects? Disconnects are not uncommon. What do you do when a client reconnects? Does it give you the InstanceID so that you can associate it with the new WebSocket handler?
The timer Logs the InstanceID. This shows that the Instance is still running.
What do you do if you catch a time out?
 
Last edited:

lip

Active Member
Licensed User
No. How are you tracking your connections via your ThreadsafeMap? At every connection, are you making sure that the client has connected previously and then updating that clients WebSocket information instead of adding it again? How are you handling time outs - in other words, when and how do you remove WebSocket information from your ThreadsafeMap when a client times out instead of properly logging off/unregistering itself? Show some code.
Each physical device has a unique IntegerID. The ThreadsafeMap uses this integer as the Key. Once a Collator (Pi) has connected it Puts an entry into the map:-

B4X:
Dim myCollator As CollatorData
    myCollator.Initialize
    myCollator.SN = CollatorSN                        'Unique SerialNumber (Int)
    myCollator.Postcode = Postcode
    myCollator.ws = ws                                'Websocket Connection
    myCollator.ParamsMap = CollatorParamMap   
    myCollator.LastCommunicated = DateTime.Now
    myCollator.TimeIdentified = DateTime.Now

Main.CollatorMap.Put(myCollator.SN,myCollator)        'ThreadsafeMap
 

OliverA

Expert
Licensed User
Do you ever remove anything from Main.CollatorMap?
 

lip

Active Member
Licensed User
But what do you do with this InstanceID? Do you give this to the client and the client uses this to identify itself? What do you do about WebSocket disconnects? Disconnects are not uncommon. What do you do when a client reconnects? Does it give you the InstanceID so that you can associate it with the new WebSocket handler?

What do you do if you catch a time out?
I do nothing with the InstanceID. I only put this in place to try and debug what is going on because I could see Timers ticking on instances that I knew were not (uniquely) associated with a physical device. I also created a ThreadSafeMap which mapped the InstanceID to the Time is was created.

I also created a third ThreadsafeMap which mapped the Wesocket connection's SessionID to the Websocket. From what I can see:-

- Each Physical Device ends up with an Entry in the Collator Map

- There are some multiple InstanceID entries with duplicate SessionIDs (So one Session disconnects and reconnects to a new instance with the same SessionID)

- There are multiple IntanceIDs, and SessionIDs for the same CollatorSN (unique physical device). So connections disconnect, leaving an orphaned Instance and then sometimes reconnect with the same SessionID and sometimes with a new SessionID.
 

OliverA

Expert
Licensed User
- There are multiple IntanceIDs, and SessionIDs for the same CollatorSN (unique physical device). So connections disconnect, leaving an orphaned Instance and then sometimes reconnect with the same SessionID and sometimes with a new SessionID.
I also created a third ThreadsafeMap which mapped the Wesocket connection's SessionID to the Websocket. From what I can see:-
Why do you need three maps? The map you posted above is fine. Do not worry about new instances. New instances are created after a WebSocket disconnects and then reconnects. It is up to you do re-associate you existing SessionID with the new websocket. If you create a new SessionID for a session that already existed, then that is an issue on your end. Workflow:

A) Client connects, creating new WebSocket instance. Client can either be
1) A new client. Generate a new CollatorSN. Generate a new CollatorData object and fill it. Place new CollatorData object into Main.CollatorMap using newly generated CallatorSN as key
2) An existing client. Use this client's CollatorSN and look it up in Main.CollatorMap.
a) If found, retrieve CollatorData and update any relevant information. Especially, assign the new WebSocket instance to CollatorData.ws
b) If not found, then
1) Either keep CollatorSN, create a new CollatorData object and fill it. Place new CollatorData object into Main.CollatorMap
2) Perform steps A.1

On top of that, set up a timer to manage time-out clients

When timer fires, step through Main.CollatorMap and check each client to see if they timed out. If they timed out, then remove from Main.CollatorMap.

With this setup, you should only have one active WebSocket connection per active client. When a client reconnects with an existing CollatorSN and you perform A.2.a, the previous WebSocket instance becomes unassigned (nothing is referencing it anymore) and whenever Java's garbage collector kicks in that instance will be cleaned up. If you keep multiple lists pointing to the WebSocket instance, you have to remove that instance from all lists or the garbage collector will not clean up the instance since it is still referenced by your application. One issue with this setup can be that if the timeout period is high, a bunch of WebSocket connections from new clients could create a bunch of new Main.CollatorMap entries and could exhaust your memory. But that would have to be a lot of new connections where new CollatorSN's would have to be created. If that is a concern, then you have to create another ThreadSafeMap that has the WebSocket instance as the key and the CollatorSN as the value. In your WebSocket Closed event you would have to then

1) Get the CollatorSN from this new ThreadSafeMap.
2) See if CollatorSN exists in Main.CollatorMap and if so remove it
3) Remove the WebSocket instance from the new ThreadSafeMap

I just don't know if it would be worth the effort to keep two maps, nor do I see a need for a third map. If your mapping becomes that convoluted, you may look into using a local SQLite database to store your extra information (creating your own session database). With such a database, no WebSocket instances are referenced and you would not have to worry about having stuck references that the garbage collector cannot clean up.
 
  • Like
Reactions: lip

lip

Active Member
Licensed User
Do you ever remove anything from Main.CollatorMap?
Yes. On WebSocket_Disconnected:-
B4X:
        Try
            Dim myResponse As Object
                myResponse = CollatorMap.Remove(CollatorSN)
            If myResponse <> Null Then
                LogwithTime("Collator " & CollatorSN & " at " & myCollator.Postcode & " removed from map successfully", False)
            Else
                LogwithTime("Collator " & CollatorSN & " not in map so not removed",False)
            End If
        Catch
            LogwithTime("Collator " & CollatorSN & " not removed from map as .Remove Failed",False)
        End Try
 

lip

Active Member
Licensed User
Why do you need three maps? The map you posted above is fine. Do not worry about new instances. New instances are created after a WebSocket disconnects and then reconnects. It is up to you do re-associate you existing SessionID with the new websocket. If you create a new SessionID for a session that already existed, then that is an issue on your end. Workflow:

A) Client connects, creating new WebSocket instance. Client can either be
1) A new client. Generate a new CollatorSN. Generate a new CollatorData object and fill it. Place new CollatorData object into Main.CollatorMap using newly generated CallatorSN as key
2) An existing client. Use this client's CollatorSN and look it up in Main.CollatorMap.
a) If found, retrieve CollatorData and update any relevant information. Especially, assign the new WebSocket instance to CollatorData.ws
b) If not found, then
1) Either keep CollatorSN, create a new CollatorData object and fill it. Place new CollatorData object into Main.CollatorMap
2) Perform steps A.1

On top of that, set up a timer to manage time-out clients

When timer fires, step through Main.CollatorMap and check each client to see if they timed out. If they timed out, then remove from Main.CollatorMap.

With this setup, you should only have one active WebSocket connection per active client. When a client reconnects with an existing CollatorSN and you perform A.2.a, the previous WebSocket instance becomes unassigned (nothing is referencing it anymore) and whenever Java's garbage collector kicks in that instance will be cleaned up. If you keep multiple lists pointing to the WebSocket instance, you have to remove that instance from all lists or the garbage collector will not clean up the instance since it is still referenced by your application. One issue with this setup can be that if the timeout period is high, a bunch of WebSocket connections from new clients could create a bunch of new Main.CollatorMap entries and could exhaust your memory. But that would have to be a lot of new connections where new CollatorSN's would have to be created. If that is a concern, then you have to create another ThreadSafeMap that has the WebSocket instance as the key and the CollatorSN as the value. In your WebSocket Closed event you would have to then

1) Get the CollatorSN from this new ThreadSafeMap.
2) See if CollatorSN exists in Main.CollatorMap and if so remove it
3) Remove the WebSocket instance from the new ThreadSafeMap

I just don't know if it would be worth the effort to keep two maps, nor do I see a need for a third map. If your mapping becomes that convoluted, you may look into using a local SQLite database to store your extra information (creating your own session database). With such a database, no WebSocket instances are referenced and you would not have to worry about having stuck references that the garbage collector cannot clean up.

Thank you so much for all of your help here. I think I have nearly enough to make a few changes and try it.

One more question: How do I detect a timeout? I have timers that tick every minute sending a small heartbeat to keep the Websocket connection alive. If I could detect that they had timed out I could reduce this traffic.

I think I'm mainly worrying about nothing: I only use the one Map, the other two were purely to examine what is going on and I suspect now and causing the problem by keeping a reference to something that the garbage collector would otherwise clear up.
 

OliverA

Expert
Licensed User
I also created a third ThreadsafeMap which mapped the Wesocket connection's SessionID to the Websocket. From what I can see:
But where are you removing this? If you still have the WebSocket in this map, the WebSocket will not be cleaned up via the garbage collector, since you are still referencing it.
 

techknight

Well-Known Member
Licensed User
This is a central server (live version running on a cloud-based Ubuntu server and a development version on a Raspberry Pi) that relays messages between Android Tablets and Raspberry Pi's. A message from a Tablet might be sent to multiple Pi's and the responses sent back to the sending Tablet. The thread-safe map in the Main activity allows Connections to send messages to the appropriate Websocket connection based on finding the appropriate Key of the map. Without this I'm not sure how I could identify the Websocket connection?
This is just my opinion, but this is screaming for the use of MQTT. Much more simple than juggling websockets.
 

lip

Active Member
Licensed User
This is just my opinion, but this is screaming for the use of MQTT. Much more simple than juggling websockets.
Oooh err...This looks interesting. Maybe I have wasted the last year of my life reinventing a (wobbly) wheel...
 
Top