vahidhedayati / grails-wschat-plugin

Grails websocket chat Plugin provides a multi-chat room add-on to an existing grails based site. provides: Private Messaging/WebRTC/Offline PM + room booking/reservations. Websocket TicTactoe. Add Live Chat to your Grails application
http://grails.org/plugin/wschat
Apache License 2.0
22 stars 10 forks source link

Concurrent Modification Exception #3

Closed grandec closed 9 years ago

grandec commented 9 years ago

Hi, I use this plugin and the "Concurrent Modification Exception" is thrown when i create a Session, disconnect, and create it again. The exception happens in the "sendUserList" method, i think it happens because when it is iterating to send all the user that a user has disconnected, someone enters the room and the map is updated with the new user while it is iterating. Even if the Set is set as synchronized, the iterator is not, so the problem happens.

vahidhedayati commented 9 years ago

Hi Grandec

Can you please confirm your grails app version Java version etc.

I have tested it on Linux Java 7 and grails 2.4.4

Grails wschat plugin as both inline and used 1.8

This error was not thrown with the following tests carried out:

  1. Logged in as a user went back on browser and then clicked login again to go back in chat room
  2. Two different users logged in from two browsers, did the same as above on either side.
  3. Enabled admin mode - and kicked one of the users and with that same user tried to login again by going back and login again.

I put a lot of checks around the iterator to ensure it was actually active/valid i.e.

Iterator<Session> iterator2=chatroomUsers?.iterator()
            if (iterator2) {
                while (iterator2?.hasNext())  {
                    def crec2=iterator2?.next()
                    if (crec2.isOpen()) {

Which should catch any issues around this.

I have done some other tweeks and will update 1.8-SNAPSHOT soon, it may just be down to grails version as well since I do recall seeing those errors ages back although the app still worked ok and front end users would have not really seen any problems.

vahidhedayati commented 9 years ago

Yep seen this now on 2.4.2 - Its related to when user closes the window down which triggers the window.onbeforeunload = function() {

am looking into it thanks

grandec commented 9 years ago

My java version: java version "1.7.0_51" Java(TM) SE Runtime Environment (build 1.7.0_51-b13) Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode)

My grails version is 2.4.4:

What i think it is happening, when a user logout, this code is called : "Iterator iterator2=chatroomUsers?.iterator() if (iterator2) { while (iterator2?.hasNext()) { def crec2=iterator2?.next() if (crec2.isOpen()) {"

While the code is in this loop, sending everyone that the user has left, a user enters the room, and this code is executed at "handleOpen" :

chatroomUsers.add(userSession)

so, while the iterator is iterating in the Set, the Set itself is changed at handleOpen throwing the exception, i think the check should be done before the "chatroomUsers.add(userSession)" at handleOpen.

This exception happens a lot in my application, because i use the chat not for "human users", but "machine users", that enters the chat, send message and leave the chat, and enter again, so this few milliseconds happens to trigger the exception.

Thanks

grandec commented 9 years ago

Wow, when i was writing a response, i didnt see your second response, hope you find the source of the problem soon :) Thank you.

grandec commented 9 years ago

how about using a ConcurrentHashMap or a CopyOnWriteArrayList ??

grandec commented 9 years ago

The Stacktrace:

java.util.ConcurrentModificationException at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(Unknown Source) at java.util.LinkedHashMap$KeyIterator.next(Unknown Source) at java_util_Iterator$next$0.call(Unknown Source) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callSafe(AbstractCallSite.java:75) at grails.plugin.wschat.ChatUtils.sendUsers(ChatUtils.groovy:441) at grails.plugin.wschat.ChatUtils.this$2$sendUsers(ChatUtils.groovy) at grails.plugin.wschat.ChatUtils$this$2$sendUsers$0.callCurrent(Unknown Source) at grails.plugin.wschat.ChatUtils.verifyAction(ChatUtils.groovy:113) at grails.plugin.wschat.ChatUtils.this$2$verifyAction(ChatUtils.groovy) at sun.reflect.GeneratedMethodAccessor8456.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90) at groovy.lang.MetaClassImpl.invokeMissingMethod(MetaClassImpl.java:919) at groovy.lang.MetaClassImpl.invokePropertyOrMissing(MetaClassImpl.java:1256) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1209) at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:1110) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1016) at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:145) at grails.plugin.wschat.WsChatEndpoint.handleMessage(WsChatEndpoint.groovy:69) at sun.reflect.GeneratedMethodAccessor6223.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBase.onMessage(PojoMessageHandlerWholeBase.java:80) at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:393) at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:494) at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:289) at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:130) at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:57) at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler$WsReadListener.onDataAvailable(WsHttpUpgradeHandler.java:203) at org.apache.coyote.http11.upgrade.AbstractServletInputStream.onDataAvailable(AbstractServletInputStream.java:194) at org.apache.coyote.http11.upgrade.AbstractProcessor.upgradeDispatch(AbstractProcessor.java:95) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:653) at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:277) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2403) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2392) at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Unknown Source)

vahidhedayati commented 9 years ago

Hi Grandec

I have updated and released a snapshot for now, it appears to be working much better.

I was going to change to concurrenthashmap but then came across this:

http://www.javacodegeeks.com/2013/05/experiment-with-concurrenthashmap.html

So in short the method remains the same, the issue was how I was going about triggering the logout when the session was ended abruptly by end user.

I have revisited the segment which deals with onbeforeunload and the disco method within chatutils.

What was occuring earlier when I closed the browser is no longer returning any errors on the backend and appears to disconnect user fine.

try

compile ":wschat:1.8-SNAPSHOT" 

Let me know how it goes

Regards vahid

grandec commented 9 years ago

Ok, Thank you very much Im gonna try here, and let you know if the exception happens again.

Regards

grandec commented 9 years ago

I updated the version but the error is still happening :(

The Stacktrace as follows:

java.util.ConcurrentModificationException at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(Unknown Source) at java.util.LinkedHashMap$KeyIterator.next(Unknown Source) at java_util_Iterator$next$0.call(Unknown Source) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callSafe(AbstractCallSite.java:75) at grails.plugin.wschat.ChatUtils.broadcast(ChatUtils.groovy:613) at grails.plugin.wschat.ChatUtils.this$2$broadcast(ChatUtils.groovy) at grails.plugin.wschat.ChatUtils$this$2$broadcast$4.callCurrent(Unknown Source) at grails.plugin.wschat.ChatUtils.sendUsersLoggedOut(ChatUtils.groovy:462) at grails.plugin.wschat.ChatUtils.verifyAction(ChatUtils.groovy:153) at grails.plugin.wschat.ChatUtils.this$2$verifyAction(ChatUtils.groovy) at sun.reflect.GeneratedMethodAccessor8802.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90) at groovy.lang.MetaClassImpl.invokeMissingMethod(MetaClassImpl.java:919) at groovy.lang.MetaClassImpl.invokePropertyOrMissing(MetaClassImpl.java:1256) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1209) at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:1110) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1016) at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:145) at grails.plugin.wschat.WsChatEndpoint.handleMessage(WsChatEndpoint.groovy:77) at sun.reflect.GeneratedMethodAccessor8801.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBase.onMessage(PojoMessageHandlerWholeBase.java:80) at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:393) at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:494) at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:289) at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:130) at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:57) at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler$WsReadListener.onDataAvailable(WsHttpUpgradeHandler.java:203) at org.apache.coyote.http11.upgrade.AbstractServletInputStream.onDataAvailable(AbstractServletInputStream.java:194) at org.apache.coyote.http11.upgrade.AbstractProcessor.upgradeDispatch(AbstractProcessor.java:95) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:653) at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:277) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2403) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2392) at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

vahidhedayati commented 9 years ago

Ok thanks, the lines of the errors have moved on - will trace this now on my end and see what is causing this - try to clear up this issue too - its to do with the methods used to close it must be different to how I was closing my session earlier.

vahidhedayati commented 9 years ago

From what I can see this is all bound to the new process put in place - and it simply points back to the bit that broadcasts to the room that the person has left.. nothing more/less.

Sorry can you please try again I have uploaded a new snapshot just add 1 to the end

1.8-SNAPSHOT1

grandec commented 9 years ago

Pressed the wrong button, sorry

grandec commented 9 years ago

Ok, thanks a lot. But im just going to be able to try this again tomorrow morning. As soon as possible im going to test it and give you the feedback

vahidhedayati commented 9 years ago

no problems all I did for this was to remove the segment that send the notification that user has left - hope it fixes it

vahidhedayati commented 9 years ago

Actually try :

compile ":wschat:1.8-SNAPSHOT2" 

and then it might even be better in your case you can disable joirners/leavers comment that is broadcasted to all..

/* 
*If you do not set left/join room to a value by default
* it will log joiners/leavers. You can set the timeout value 
* by default to 0 to a ms value of your preference if user out/in is quick
* This may slow down msg being sent..
* Logleavers sets a log.error to a value of current username room to verify
* abnormalities in the username (spaces and so forth)
* by default this is no
* so set a timeout if you wish which is only worthy if send.leftroom='yes'
* 
*/ 
wschat.left.timeout='1000'
wschat.send.leftroom='no'
wschat.logleavers='yes'
wschat.send.joinroom='no'
grandec commented 9 years ago

I tried now, and still having problems, but it seems that the stacktrace changed a little bit. The two stacktraces happens in different parts of the code, but still both happens at

"Iterator iterator=chatroomUsers?.iterator() if (iterator) { while (iterator?.hasNext()) { def crec=iterator?.next()"

Here two different stacktraces:

1:

java.util.ConcurrentModificationException at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(Unknown Source) at java.util.LinkedHashMap$KeyIterator.next(Unknown Source) at java_util_Iterator$next$0.call(Unknown Source) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callSafe(AbstractCallSite.java:75) at grails.plugin.wschat.ChatUtils.privateMessage(ChatUtils.groovy:713) at grails.plugin.wschat.ChatUtils.this$2$privateMessage(ChatUtils.groovy) at grails.plugin.wschat.ChatUtils$this$2$privateMessage$3.callCurrent(Unknown Source) at grails.plugin.wschat.ChatUtils.verifyAction(ChatUtils.groovy:171) at grails.plugin.wschat.ChatUtils.this$2$verifyAction(ChatUtils.groovy) at sun.reflect.GeneratedMethodAccessor8921.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90) at groovy.lang.MetaClassImpl.invokeMissingMethod(MetaClassImpl.java:919) at groovy.lang.MetaClassImpl.invokePropertyOrMissing(MetaClassImpl.java:1256) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1209) at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:1110) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1016) at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:145) at grails.plugin.wschat.WsChatEndpoint.handleMessage(WsChatEndpoint.groovy:77) at sun.reflect.GeneratedMethodAccessor8920.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBase.onMessage(PojoMessageHandlerWholeBase.java:80) at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:393) at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:494) at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:289) at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:130) at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:57) at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler$WsReadListener.onDataAvailable(WsHttpUpgradeHandler.java:203) at org.apache.coyote.http11.upgrade.AbstractServletInputStream.onDataAvailable(AbstractServletInputStream.java:194) at org.apache.coyote.http11.upgrade.AbstractProcessor.upgradeDispatch(AbstractProcessor.java:95) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:653) at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:277) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2403) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2392) at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Unknown Source)

2:

java.util.ConcurrentModificationException at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(Unknown Source) at java.util.LinkedHashMap$KeyIterator.next(Unknown Source) at java_util_Iterator$next$0.call(Unknown Source) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callSafe(AbstractCallSite.java:75) at grails.plugin.wschat.ChatUtils.sendUsers(ChatUtils.groovy:550) at grails.plugin.wschat.ChatUtils.this$2$sendUsers(ChatUtils.groovy) at grails.plugin.wschat.ChatUtils$this$2$sendUsers$0.callCurrent(Unknown Source) at grails.plugin.wschat.ChatUtils.verifyAction(ChatUtils.groovy:129) at grails.plugin.wschat.ChatUtils.this$2$verifyAction(ChatUtils.groovy) at sun.reflect.GeneratedMethodAccessor8921.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90) at groovy.lang.MetaClassImpl.invokeMissingMethod(MetaClassImpl.java:919) at groovy.lang.MetaClassImpl.invokePropertyOrMissing(MetaClassImpl.java:1256) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1209) at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:1110) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1016) at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:145) at grails.plugin.wschat.WsChatEndpoint.handleMessage(WsChatEndpoint.groovy:77) at sun.reflect.GeneratedMethodAccessor8920.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBase.onMessage(PojoMessageHandlerWholeBase.java:80) at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:393) at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:494) at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:289) at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:130) at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:57) at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler$WsReadListener.onDataAvailable(WsHttpUpgradeHandler.java:203) at org.apache.coyote.http11.upgrade.AbstractServletInputStream.onDataAvailable(AbstractServletInputStream.java:194) at org.apache.coyote.http11.upgrade.AbstractProcessor.upgradeDispatch(AbstractProcessor.java:95) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:653) at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:277) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2403) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2392) at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Unknown Source)

vahidhedayati commented 9 years ago

Hi Yes

It has moved on it seems: (is it any better ? ) i.e. is this reduced amount of incidents?

1st stack trace:

Private message issue at grails.plugin.wschat.ChatUtils.privateMessage(ChatUtils.groovy:713) PrivateMessage - next Iterator at grails.plugin.wschat.ChatUtils.this$2$privateMessage(ChatUtils.groovy) at grails.plugin.wschat.ChatUtils$this$2$privateMessage$3.callCurrent(Unknown Source) at grails.plugin.wschat.ChatUtils.verifyAction(ChatUtils.groovy:171) -> /pm verification action - i.e. privateMessage(user,myMsg,userSession)

2nd Stacktrace : Connector issue at grails.plugin.wschat.ChatUtils.sendUsers(ChatUtils.groovy:550) (next iterator of sendusers) at grails.plugin.wschat.ChatUtils.this$2$sendUsers(ChatUtils.groovy) at grails.plugin.wschat.ChatUtils$this$2$sendUsers$0.callCurrent(Unknown Source) at grails.plugin.wschat.ChatUtils.verifyAction(ChatUtils.groovy:129) ---> SendUsers

Could try stop second issue by giving override actions to disable sending userList on connection... Would this be of use?

I do think what ever you are doing is super quick as you have stated causing all of this, how are you doing it ? using curl if so take a look at the comment on this answer putting in some form of sleep

http://unix.stackexchange.com/questions/129257/curl-url-call-in-for-loop

It might be that I need to move to concurrent hashmap to stop this issue for this scenario - am putting things into a tidy struture at the moment so will post back a comment when I have next snap shot ready

grandec commented 9 years ago

It seems that improved a litte, as it is not happening where it used to happen anymore.

It indeed is super quick, here is the part of code that connects and disconnects, this code is executed by many users.

As you can see in this code. The connectRoom method simply connects via websocket to the room given at the parameter, and returns the session.

def oSession = messengerClientService.connectRoom("threatment"); // do stuff messengerClientService.disconnect(oSession);
def oSession2 = messengerClientService.connectRoom("apps"); //do stuff messengerClientService.disconnect(oSession2);

grandec commented 9 years ago

It seems that if I connect all the sessions, do all the Stuff, and later on disconnect, the error happens less often, but sometimes still happens.

def oSession = messengerClientService.connectRoom("threatment"); def oSession2 = messengerClientService.connectRoom("apps");

// do stuff from oSession //do stuff from oSession2

messengerClientService.disconnect(oSession); messengerClientService.disconnect(oSession2);

ps: the disconnect method simply sends the disconnector string to the server, and the server that closes the connection, just like is happening at the javascript.

vahidhedayati commented 9 years ago

Wondering if putting in logic of connect. do stuff then sleep for about 5-10 seconds and attempt to disconect would help at all

grandec commented 9 years ago

With sleep seems that the error is not happening, but the rendering of the gsp after this is delayed too because it is rendered after this method ends. Unfortunately another error happened :( but i think this error is something wrong with my clientEndPoint

java.lang.IllegalStateException: The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for called method at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.checkState(WsRemoteEndpointImplBase.java:1052) at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.textStart(WsRemoteEndpointImplBase.java:1015) at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.sendString(WsRemoteEndpointImplBase.java:171) at org.apache.tomcat.websocket.WsRemoteEndpointBasic.sendText(WsRemoteEndpointBasic.java:37) at sun.reflect.GeneratedMethodAccessor8919.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoCachedMethodSite.invoke(PojoMetaMethodSite.java:189) at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116) at grails.plugin.wschat.ChatUtils.sendUserList(ChatUtils.groovy:420) at grails.plugin.wschat.ChatUtils.this$2$sendUserList(ChatUtils.groovy) at grails.plugin.wschat.ChatUtils$this$2$sendUserList$2.callCurrent(Unknown Source) at grails.plugin.wschat.ChatUtils.sendUsersLoggedOut(ChatUtils.groovy:533) at grails.plugin.wschat.ChatUtils.verifyAction(ChatUtils.groovy:162) at grails.plugin.wschat.ChatUtils.this$2$verifyAction(ChatUtils.groovy) at sun.reflect.GeneratedMethodAccessor8921.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90) at groovy.lang.MetaClassImpl.invokeMissingMethod(MetaClassImpl.java:919) at groovy.lang.MetaClassImpl.invokePropertyOrMissing(MetaClassImpl.java:1256) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1209) at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:1110) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1016) at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:145) at grails.plugin.wschat.WsChatEndpoint.handleMessage(WsChatEndpoint.groovy:77) at sun.reflect.GeneratedMethodAccessor8920.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBase.onMessage(PojoMessageHandlerWholeBase.java:80) at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:393) at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:494) at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:289) at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:130) at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:57) at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler$WsReadListener.onDataAvailable(WsHttpUpgradeHandler.java:203) at org.apache.coyote.http11.upgrade.AbstractServletInputStream.onDataAvailable(AbstractServletInputStream.java:194) at org.apache.coyote.http11.upgrade.AbstractProcessor.upgradeDispatch(AbstractProcessor.java:95) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:653) at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:277) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2403) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2392) at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Unknown Source)

vahidhedayati commented 9 years ago

ok i think that error may be rather straight forward http://stackoverflow.com/questions/22257079/java-websockets-the-remote-endpoint-was-in-state-text-full-writing - I have changed it to void for handle open - so should hopefulyl resolve this issue for next release.

how about reducing sleep to half a second sleep(500) or if this don't work TimeUnit.MILLISECONDS.sleep(500)

So although it sleeps to the human eye not so recognisable -

vahidhedayati commented 9 years ago

just released 1.9 and it might be better or probably a lot worse HEH - Moved a lot of the stuff around from src/groovy to services so hope it all still works. The chatroomUsers is now in an interface https://github.com/vahidhedayati/grails-wschat-plugin/blob/master/src/groovy/grails/plugin/wschat/ChatSessions.java

vahidhedayati commented 9 years ago

playing around with concurrentHashMap at the moment so when I have a working version will release the next snap shot - might be in the next few days or week and I will update this issue - hopefully then you can do all of this with no sleep times in code.

grandec commented 9 years ago

Thanks a lot for all the help. I'm gonna update to 1.9 and start using it with sleeptime for awhile. O hope concurrentHashMap doenst give you headaches :smile:

grandec commented 9 years ago

How about using the forEach method instead of using the iterator?. It seems that the forEach method is threadSafe, so the ConcurrentModificationException may not happen

http://stackoverflow.com/questions/23450227/is-iteration-via-collections-synchronizedset-foreach-guaranteed-to-be-thr

grandec commented 9 years ago

Nevermind :disappointed: i Just saw that you use two nested iterators of chatroomUsers in some parts of the code, so it can lead to a deadlock if the iterator is changed to forEach. Maybe an iterator inside the forEach works instead of two nested forEach.

vahidhedayati commented 9 years ago

Cool will try a test of for each and I know where the dual iterator call is I think it was related to chat/cam user. Will see what comes out. If it works will release it as snapshot and let you know. The concurrent hash map is a little more complex than the Sets initialisation so am trying mess around and get it to be initialised upon main socket initialisation. ☺

Intrigued by your work on top is it like a server monitor where hosts alert to chat room or should I sat private message haha very. Cool

vahidhedayati commented 9 years ago

Some updates.

Thanks for that link earlier, had time to have a read and decided to opt for a less painful option and go for advice lower down:

http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedCollection-java.util.Collection-

Basically changed from Set to Collection and then before each iterator call a new

 synchronized (chatroomUsers) {
  ...
 }

is now added..

See how it goes with new release of 1.10

vahidhedayati commented 9 years ago

and if not then try 1.10-SNAPSHOT - this is split that nested iterator - iterators removed and just doing a simple .each { also back to Set from collection... hoping 1 of these 2 releases has a better outcome

grandec commented 9 years ago

I'm gonna try the 1.10 now. I'm using the chat to be an interface where different applications communicate with each other, and a messaging system as well. A simple example is: In one of my applications, it opens an easyui tab to another application inside it. The other application cannot execute javascripts in the context of the first application, for example, window.somefunction does not works, the browser does not allow it. So the second application of the user, sends a message to the first application of the user telling, "hey, close this tab", or wathever another function the first application can call but the seconds cant. So this is a Client-client communication, the server of the application is not involved. But there is another use, where the server of the application is involved. With the chat, the server enters the chat, tells some user that there is a new notification, the notification icon is updated(like facebook), and then the server leaves the chat. These are just few examples :smile:

vahidhedayati commented 9 years ago

that sounds impressive, I hope one of these works today. If not it will have to be concurrent method

grandec commented 9 years ago

Used the 1.10 version and IT WORKED!!! :3 :smile: :tada: :tada:

Thanks a lot for all the support :)

vahidhedayati commented 9 years ago

ok - wow cool - so what about the new snapshot version - if this works - then we would have also got rid of that clonky iterator method - if it don't then I will change snapshot code to be 1.10's code..

grandec commented 9 years ago

I'm gonna try the snapshot version now

grandec commented 9 years ago

The Snapshot version worked as well :+1: :smile:

vahidhedayati commented 9 years ago

ok woot - :) It may have been related to a few other minor issues - the dual nested loop - obviously not defining the synchronized method previously and there was 1 instance were it didn't fully check if crec.isopen it just said if (crec).. Well thanks. Its made me tidy up and split the code which is something I been meaning to do for a while.

grandec commented 9 years ago

Thanks a lot for all the effort.

Which version should I use? the 1.10 or the 1.10-Snapshot? is the snapshot going to replace the 1.10?

vahidhedayati commented 9 years ago

Its up to you , I guess stay with the snapshot since from here on it will be based on this method and if there are issues its best spotted before it becomes 1.11 soon.

If snapshot ends up causing issues might be worth rolling back to 1.10 and seeing if that is acually a more stable method.

I will release 1.11 sometime soon which I want to add some UI stuff to current snapshot - allow user to change theme by clicking front end buttons.

Thanks for all your feedback

I do have some other concepts for this but its mostly webrtc based i.e. letting users share/send screen

Booking rooms for specified period between users (Pfft haha sounds like a wishlist for 2099)

grandec commented 9 years ago

Thank you!! Merry Christmas, and a happy new year. If I find another problem I'll be back :+1: See ya.

vahidhedayati commented 9 years ago

Hey you had me thinking so I have added a new feature that will be in 1.11

https://www.youtube.com/watch?v=zAySkzNid3E https://github.com/vahidhedayati/grails-wschat-plugin/wiki/wsChatClientEndPoint-new-feature-since-1.11

Thanks for the ideas, maybe you can tell me how you are handing messages since in this case it starts to distance itself from original taglib

grandec commented 9 years ago

Hi, I created a javascript file named "chat.js", the main layout of my pages includes this file. At this main layout a method called "initChat" is called, in this initChat method, the client connects to a room in the server.

initChat("${utils.Application.getUrl(utils.Application.MESSENGER_APP)}/WsChatEndpoint", "${utils.Application.cfgApp.id}", "${Long.valueOf(session[utils.Application.sessionId]?:0)}", "${utils.Application.config.messenger.room.app}");

The first parameter is the URL of the Messenger Application, where the wschat plugin is located. The second is the Application Id. As I said before, many different application connects to the messenger, the application Id is used to create the name of the user to connect to the chat. The third is the id of the user connected to the application. And the fourth is the room that the user is going to connect. The name of the user in the chat after it is connected is "applicationName-userId-roomName", i had to include the roomName at the name because the wschat does not accept user with the same name, even at different rooms.

If there is an IFrame inside one application connecting to another, the application inside the IFrame connects to the chat as well, but the applicationName will be different, so the two applications can communicate with each other using the chat. The communication at the chat is via JSON. A JSON String is sent to a specific user, or broadcasted. All the clients then receive the JSON string, parse it, process it, and then execute the command passed. Ex:

{ "command":"executeJavascript", "arguments":"console.log('hi');"}

When this command is received, it is processed, and the executeJavascript command calls an eval that executes the string that is inside the arguments. This is the execution client-client, but there is the execution client-server as well.

Below the processMessage Method, that is called when a message is received:

function processMessage(connectionInfo, message) {
    if (connectionInfo) {
        try {
            var jsonData = JSON.parse(message.data);
            if(jsonData.users !== undefined){
                var users = jsonData.users;
                for(var i = 0; i < users.length; i++){
                    if(users[i].user !== undefined){
                        connectionInfo['userList'].push(users[i].user);
                    }
                }
                //userList = unique(userList);
            }
            if(jsonData.owner !== undefined){
                connectionInfo['userList'].push(jsonData.owner);
                connectionInfo['userList'] = unique(connectionInfo['userList']);
            }
            var jsonMessage = jsonData.message || jsonData.privateMessage;
            if(jsonMessage !== undefined){
                var joinedIndex = jsonMessage.indexOf("has joined");
                if(joinedIndex != -1){
                    var user = jsonMessage.substring(0, joinedIndex).replace(/ /g, "");
                    connectionInfo['userList'].push(user);
                    unique(connectionInfo['userList']);
                }else{
                    var leftIndex = jsonMessage.indexOf("has left");
                    if(leftIndex != -1){
                        var user = jsonMessage.substring(0, leftIndex).replace(/ /g, "");
                        var i = connectionInfo['userList'].indexOf(user);
                        if(i != -1) {
                            connectionInfo['userList'].splice(i, 1);
                        }
                    }
                }
            }

            var indexOf = -1;
            var lastIndexOf = -1;
            if (jsonMessage != undefined) {
                indexOf = jsonMessage.indexOf('{');
                lastIndexOf = jsonMessage.lastIndexOf('}');
            }
            var messages = "";
            if (indexOf != -1 && lastIndexOf != -1) {
                messages = jsonMessage.substring(indexOf, lastIndexOf + 1);
                messages = JSON.parse(messages);
                return processCommands(messages);
            }
        }
        catch (e) {
            return null;
        }
    }
    return null;
}

And a part of the processCommand method

function processCommands(jsonCommands) {
    if(jsonCommands.command !== undefined){
        switch(jsonCommands.command){
            case "javascript":
                executeJavascript(jsonCommands.arguments);
                break;
            case "closeTab":
                var args = getParametersFromMessage(jsonCommands.arguments);
                var oFunction = tabRemoveActiveInMain || window.parent.tabRemoveActiveInMain;
                oFunction(args[0], args[1]);
                break;
                .
                .
                .
        }
    }
}

At the server side, I created a service to manage the chat, it is called messengerClientService.

this is the connect method at the service:

private static final String CONNECTOR = "CONN:-"
private static final String DISCONNECTOR = "DISCO:-";

private static Session p_connect(String _uri, String _username, String _room){
    String oRoom = _room?:Application.config.messenger.room.app;
    URI oUri;
    if(_uri){
        if(_uri.endsWith('/')){
            oUri = URI.create(_uri+oRoom);
        }else{
            oUri = URI.create("$_uri/$oRoom");
        }
    }else{
        String uriServer = "${Application.config.url.Application.messenger}".replace('wss', 'ws').replace('8443','8080');
        oUri = URI.create("${uriServer}/WsChatEndpoint/$oRoom")
    }
    def container = WsContainerProvider.getWebSocketContainer();
    Session oSession;
    String oUsername = _username?:"$Application.cfgApp.id-server[${(Math.random()*1000).intValue()}]-$oRoom";
    try{
        oSession = container.connectToServer(messengerClientEndpoint.class, oUri);
        oSession.getBasicRemote().sendText(CONNECTOR+oUsername);
    }catch(Exception e){
        e.printStackTrace();
        if(oSession && oSession.isOpen()){
            oSession.close();
        }
        return null;
    }
    oSession.userProperties.put("username", oUsername);
    return  oSession;
}

When the server connects, it used a different name pattern that is used when a client connects. It uses "applicationName-server[randomNumber-roomName] The server connects to the ChatServer that is another application which contains the wschat plugin. After connecting it send the "CONNECTOR+oUsername" String to connect to the room with the specified name, and later it returns the session.

Here is a part of the code that is executed after a support call is created, the quickConnect Closure connects to the wschat using the connect method and in a default room, and later everything inside the closure is executed, after execution, it disconnects. The quickRoomConnect is the same as the quickConnect, but connects to the specified room instead.

try {
    messengerClientService.quickRoomConnect("supportCall") { session ->
        messengerClientService.sendExecuteJavascriptToAll(session, "alertNewSupportCallCreated('" + messageAlert.toString() + "')");
    }
} catch (Exception e) {
    e.printStackTrace();
}
try {
    messengerClientService.quickConnect { session ->
        messengerClientService.alertEvent(session, "update", SupportCall.getSimpleName(), oSupportCall, [oSupportCall.requester.id]);
    }
} catch (Exception e) {
    e.printStackTrace();
}

The alertEvent Method :

public void alertEvent(def _oSession,  String _event, String _context, def _data, List<Long> _lUsersId){
    def oSession = _oSession?: connect();
    String sMessage = """{
                        "command":"event",
                        "arguments":[
                                        {
                                        "event":"$_event",
                                        "context":"$_context",
                                        "data":[${JSON.toString(_data)}]
                                        }
                                    ]
                        }
                    """;
    _lUsersId.each { Long userId ->
        sendPM(oSession, getGlobalReceiverNameFromUserId(userId) ,sMessage.replaceAll("\t","").replaceAll("\n",""));
    }
    if (_oSession == null) {
        disconnect(oSession);
    }
}

Here is the quickRoomConnect closure:

public quickRoomConnect  = {String room, Closure closure ->
    Session session = p_connect(null, null, room);
    try{
        closure(session);
    }catch(e){
        throw e;
    }
    finally {
        disconnect(session);
    }
}

The disconnect method just sends the string to disconnect to the chat server, and the chat server that closes the connection.

public Session disconnect(Session _oSession){
    try{
        if(_oSession && _oSession.isOpen()){
            sendMessage(_oSession, DISCONNECTOR);
        }
    }catch (Exception e){
        e.printStackTrace();
    }
    return _oSession
}

If you have any question, just ask me :smile:

vahidhedayati commented 9 years ago

Cool, Thanks I will look at whether its worth implementing all of this within the plugin so its available to all. But I am unsure if you have had a chance to view the video or better still look at the link showing ChatClient.

I think what I have done there is quite close to what you have done here but more through backend i.e. its a WebSocketClient, so actual applications could be a simple html based app or a grails app that has a simple gsp that triggers client/server that end user can then use CLI curl to execute client/server initiation...

The video and current 1.11 release does not include the talked of interface and hence the usage of frontend Java script on the page doing the taglib call which will be in the next release.

So currently you can use those tag libs send either 1 command or a set of defined commands that server/client can use to interact - the server/client could then be configured to override that ChatClientService and further backend actions can be taken based upon interaction (shown in the video)

I like your method due to it being full on front end - my method's front end is based upon polling

grandec commented 9 years ago

I just saw your video :D , and I really liked your implementation of these automated client-server messages. But one question, in your video, you need to enter in the "/server" page to connect the server, at that point, does the page receives the messages from the chat and send to the server via ajax or something, or it just triggers an action to the server itself connects to the chat?

edit: Nvm i just saw the https://github.com/vahidhedayati/grails-wschat-plugin/wiki/wsChatClientEndPoint-new-feature-since-1.11 page :D

vahidhedayati commented 9 years ago

ok so :)

I created two gsp pages in my test project:

Server: https://github.com/vahidhedayati/testwschat/blob/master/views/test/server.gsp

<chat:clientConnect
user="masteruser"
receiver="clientuser"
strictMode="false"
masterNode="true"
message="hi this is an automated websocket SERVER message"
actionMap="['do_task_1': 'performed_task_1', 'do_task_2': 'performed_task_2', 'do_task_3': 'performed_task_3','close_my_connection', 'close_connection', 'list_domain': 'testwschat.userbase']"
/>

Client: https://github.com/vahidhedayati/testwschat/blob/master/views/test/client.gsp

<chat:clientConnect
user="clientuser"
receiver="masteruser"
message="do_task_1"
strictMode="false"
actionMap="['performed_task_1': 'do_task_2', 'performed_task_2': 'do_task_3','performed_task_3': 'close_my_connection' ]"
/>

These pages could be sitting on two separate applications with the plugin installed on both..

I think on client the wschat plugin could be build rather than compile, since it does not need to install actual wschat end points. I need to still test it across multiple apps.

So with that in place the strict mode was left as false so this means everything got echoed into that default room, if I had also declared:

room="this_room"

Then so long as room is available both those client/servers will communicate in this room

With strictMode set to true both sides will only look for messages or PM's from the defined user within the taglib. if False another user can send a message to room or as PM to any of the users and if they have an actionMap key matching this, they will respond back with their preset response - (which you can then override in your overriden service)

So i think to answer your question, the server initially connects to the room and remains connected, the client then can come along at any point or any set of clients and send commands that could be in the actionMap of the server. So imagine it could be in any order a set of keys that it knows and what to send back then multiple clients or that one client can send their initial command to trigger interaction and have a response that they are looking to get back. If you look at client it has an initial command that server will have in its map, where as the server connects and say's hi I am here ....

The client can send close_connection to server to shut server down, or client can send a new command that server understands like close_my_connection which in server actionMap set to respond with close_connection that then closes client connection. Otherwise connections remain open..

So server needs to be there to understand client otherwise client will only send first command and not get anything back from anyone in that room that has a value that matches its own keys in its actionMap.

In that case if you wanted something to send and disconnect refer to index page which has autodisco="true" so it sends message and disconnects.

There is further work done to this which is in current github but not released: https://github.com/vahidhedayati/grails-wschat-plugin/wiki/wsChatClientEndPoint-new-feature-since-1.11

So as you saw in the video the process allows you to override what goes on from the service point of view with not much action on the html front end. I am not entirely happy with this implementation but I guess it will do for now.

So in this method, the actual WsChatClientService now implements ClientSessions which is an interface that holds a hashMap of clientSlaves/clientMaster communications.

There is also this controller that the below java script within the page making taglib polls for responses back to those hashMap collections from the interface..

https://github.com/vahidhedayati/grails-wschat-plugin/blob/master/grails-app/controllers/grails/plugin/wschat/client/WsChatClientController.groovy


<chat:clientConnect
user="masteruser" 
receiver="clientuser"
strictMode="false"
masterNode="true"
divId="serverNode"
message="hi this is an automated websocket SERVER message"
actionMap="['do_task_1': 'performed_task_1', 'do_task_2': 'performed_task_2', 'do_task_3': 'performed_task_3', 'close_my_connection': 'close_connection', 'list_domain': 'testwschat.userbase']"
/>
-
<div id="serverNode">
</div>

<div id="showMasters">
</div>
<div id="masterList">
</div>

<g:javascript>
function getOnline2() {
    var sb = [];

    $.getJSON('${createLink(controller:"wsChatClient", action: "masterList")}',function(data){
        sb.push('<table>');
        var divId='';
        data.forEach(function(entry) {
            var actionThis=entry.actionThis;
            var sendThis=entry.sendThis;
            var divId=entry.divId;
            var msgFrom=entry.msgFrom
            sb.push('<tr><td>\n');
            sb.push(actionThis+'</td><td>'+sendThis+'</td><td>'+msgFrom+'</td></tr>\n');

        });
        sb.push('</table>');
        $('#masterList').html(sb.join(""));         
    });
}

function getOnline() {
    <g:remoteFunction controller="wsChatClient" action="showMasters" update="showMasters"/>
}
function pollPage() {
    getOnline();
    getOnline2();
    setTimeout('pollPage()', 5000);
}
pollPage();
</g:javascript>

Probably the best method would be to use the $.getJSON method since from a front end point of view you could use java script to manipulate what divs you wish to update with what actions that those actionThis/sendThis values could possibly be triggers/keys to other backend data that javascript updates front end with.

There is also truncateSlave/master functions in that controller/service that you may wish to use to trim the interface files since it is polling that constantly from a front end point of view. It could be a function that you do something like above and get the controller truncate when you have done something with view or implement a stop poll function.. I have all that code too if you need to reset / pause poll - refer to jssh plugin ajax polling page

vahidhedayati commented 9 years ago

Yep I think I should release it as a snapshot since those polling features won't work on current release - it has got as far as only backend stuff as per video on the current 1.11 release.

will release 1.11-snapshot now which will make all of the above in the wiki i.e. polling work.(try in about 5 mins and snapshot version will be on grails

grandec commented 9 years ago

One thing that i'm not getting, why use polling if you have a webSocket communication between server and client?

vahidhedayati commented 9 years ago

I think to try answer your question neither client/server has any direct websocket connection where as in your example it is the front ends making similar connections that front end gsp would be in a typical chat.

This is why I am not so happy with the current solution. The reason is that I made the assumption that both client and server may not actually be websocket applications.

In this model chat server could be outside of both clients/servers models .

The server/client most importantly are not making any websocket connections themselves directly via their presence. i.e. 2 simple gsp pages that have a taglib (invisible to the rest of its content) with possibly a trigger button to re-intiate client or server if server is another website.

Example: Server running away Client being a smartphone triggers a page that calls interaction - this triggers a set command to trigger sales prices - servers gsp picks up does it on backend front end polls pick up reload page with new results.

vahidhedayati commented 9 years ago

I should also add, the service you extend: (With bean declaration)

The two things that I have not explained well enough:

  1. Sending the map to the interface : In this example which is identical to one in plugin code the map is a map of all things that happened or was passed through, you could extend and write your own rules so create your own map with own values and get fancy by saying if actionthis == 'something' set map [ frontEndDothis: 'Go_Wild' ]

So in effect manipulate here what you want the front end to pickup..

 def myMap=[pm:pm, actionThis: actionthis, sendThis: sendThis, divId:divId,
            msgFrom:msgFrom, strictMode:strictMode, masterNode:masterNode ]

        if (masterNode) {
            addon="[PROCESSED]"
            if (saveClients) {
                clientMaster.add(myMap)
            }
        }else{
            if (saveClients) {
                clientSlave.add(myMap)
            }
        }
  1. Do something with request physicall on backend - This is kind of what I meant for aboves unique map creation.
        if (masterNode) {
            if (actionthis== 'do_task_1') {
                // TODO something on master node that has mappings to do_task_1
                println "something on master node that has mappings to do_task_1 TASK1"
            }else if (actionthis== 'do_task_2') {
                // TODO something on master node that has mappings to do_task_2
                println "something on master node that has mappings to do_task_2 TASK2"
            }else if (actionthis== 'do_task_3') {
                // TODO something on master node that has mappings to do_task_3
                println "something on master node that has mappings to do_task_3 TASK3"
            }

        }

With this in place according to do_task_1 your apps back end could be doing 1 thing whilst you have manipulated and sent back to your gsp (currently polled) frontEndDothis: 'Go_Wild'

grandec commented 9 years ago

So, let me see if I understood, correct me if I'm wrong. The long polling is a solution that you try to reach all the kinds of applications, even if they do no support websocket, so the client and server communicate with each other with this opened connections, no websocket needed. But doing this dont you kill one of the important features of websocket, the 2 bytes header, instead of a (lot of bytes here) an average 700-800 bytes http header?.