Recent Changes - Search:

edit SideBar

WebSocketImplementationNotes

Summary

This module is implemented in the Cape Code Host that uses Nashorn. It should be relatively easy to implement for the Node Host using Node.js because it is fashioned after the ws module in Node.js, with additional influence from the web socket submodule of the socket.io.

Node Host

Node Test Case

Our test case is invoked using Cape Code Host Cape Code:

   $PTII/bin/vergil -capecode $PTII/org/terraswarm/accessor/test/auto/WebSocketClientJS.xml

Running the code generator creates accessors/web/net/test/auto/WebSocketClient.js, which can be invoked with:

  cd $PTII/org/terraswarm/accessor/accessors/web/net/test/auto;
  node ../../../hosts/node/nodeHostInvoke.js -timeout 2000 net/test/auto/WebSocketClientJS

The WebSocketClient accessor is never fired

When the Cape Code model above is run, we get the following output:

WebSocketClient: WebSocketClient.js: initialize() (Thread[WebSocketClientJS,1,main])
WebSocketClient: WebSocketClient.js: connect() (Thread[WebSocketClientJS,1,main])
WebSocketClient: Connection failed. Will try again: Connection refused: localhost/127.0.0.1:8087 (Thread[vert.x-eventloop-thread-1,6,main])
WebSocketServer: Server: Listening for socket connection requests. (Thread[vert.x-eventloop-thread-3,6,main])
WebSocketServer: Server: new socket established with ID: 0 (Thread[vert.x-eventloop-thread-3,6,main])
WebSocketClient: WebSocketClient.js: onOpen(): Status: Connection established (Thread[vert.x-eventloop-thread-1,6,main])
WebSocketClient: Connection closed because model is no longer running. (Thread[AWT-EventQueue-0,6,main])
WebSocketClient: Status: Connection closed in wrapup. (Thread[AWT-EventQueue-0,6,main])
WebSocketClient: WebSocketClient.js onClose(): Status: Connection closed. (Thread[vert.x-eventloop-thread-1,6,main])
WARNING: Invoking send() too late (probably in a callback), so WebSocketServer is not able to send the token {socketID = 0, status = "closed"} to the output connection. Token is discarded.
WebSocketClient: WebSocketClient.js onClose(): Status: Connection closed. (Thread[vert.x-eventloop-thread-1,6,main])
WARNING: Invoking send() too late (probably in a callback), so WebSocketServer is not able to send the token {socketID = 0, status = "closed"} to the output connection. Token is discarded.
WebSocketClient: Connection closed because model is no longer running. (Thread[WebSocketClientJS,1,main])
WebSocketClient: Status: Connection closed in wrapup. (Thread[WebSocketClientJS,1,main])
28984 ms. Memory: 448512K Free: 134833K (30%)

When the node version is run, we get:

Instantiated accessor WebSocketClientJS with class net/test/auto/WebSocketClientJS
WebSocketClient.js: initialize()
WebSocketClient.js: connect()
webSocketServer.Server()
webSocketServer.start(): localhost 8087
webSocketServer.start(): listening event
webSocketServer.stop()
undefined:121
throw new Error('The fire() function of this accessor was never called. ' +
^

Error: The fire() function of this accessor was never called. Usually, this is an error indicating that starvation is occurring.
at Accessor.exports.wrapup (eval at Accessor (/Users/cxh/ptII/org/terraswarm/accessor/accessors/web/{$HOSTS_COMMON}/commonHost.js:425:19), <anonymous>:121:23)
at Accessor.wrapup (/Users/cxh/ptII/org/terraswarm/accessor/accessors/web/{$HOSTS_COMMON}/commonHost.js:552:37)
at Accessor.wrapup (/Users/cxh/ptII/org/terraswarm/accessor/accessors/web/{$HOSTS_COMMON}/commonHost.js:545:52)
at null._onTimeout (/Users/cxh/ptII/org/terraswarm/accessor/accessors/web/{$HOSTS_NODE}/nodeHostInvoke.js:77:39)
at Timer.listOnTimeout (timers.js:92:15)

Comparing the two outputs:

  • initialize() is fine:
    • Cape Code: WebSocketClient: WebSocketClient.js: initialize() (Thread[WebSocketClientJS,1,main])
    • Node: WebSocketClient.js: initialize()
  • connect() is fine:
    • Cape Code: WebSocketClient: WebSocketClient.js: connect() (Thread[WebSocketClientJS,1,main])
    • Node: WebSocketClient.js: connect()
  • The client fails to connect!
    • Cape Code: WebSocketClient: Connection failed. Will try again: Connection refused: localhost/127.0.0.1:8087 (Thread[vert.x-eventloop-thread-1,6,main])
      • The above comes from HttpClientExceptionHandler in ptolemy/actor/lib/jjs/modules/webSocket/WebSocketHelper.java
      • The stack trace is:
        java.net.ConnectException: Connection refused: localhost/127.0.0.1:8087
                at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
                at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)
                at io.netty.channel.socket.nio.NioSocketChannel.doFinishConnect(NioSocketChannel.java:224)
                at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java\
        :289)
                at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:528)
                at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:468)
                at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:382)
                at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:354)
                at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:112)
                at java.lang.Thread.run(Thread.java:745)
      • WebSocketHelper._connectWebsocket() is a private method that instantiates a HttpClientExceptionHandler.
        • WebSocketHelper.open() invokes _connectWebsocket as does HttpClientExceptionHandler.
    • Edward wrote:
    "Next problem is that the WebSocketServer is not issuing a connection event. This may be because WebSocketClient gets initialized before the servers is listening. I think the Nashorn implementation repeatedly retries to make the connection, but it looks like maybe the Node implementation in the ws module just silently fails (?)."
  • Continuing:
    • Cape Code: WebSocketServer: Server: Listening for socket connection requests. (Thread[vert.x-eventloop-thread-8,6,main])
    • Node: webSocketServer.Server()
      • The above comes from accessors/web/hosts/hosts/node/node_modules/@accessors-modules//web-socket-server/web-socket-server.js:
        exports.Server = function (options) {
            console.log("webSocketServer.Server()");
            if (typeof options.port === 'undefined' || options.port === null) {
                this.port = 80;
            } else {
                this.port = options.port;
            }
            this.hostInterface = options.hostInterface || 'localhost';
            this.sslTls = options.sslTls || false;
            this.pfxKeyCertPassword = options.pfxKeyCertPassword || '';
            this.pfxKeyCertPath = options.pfxKeyCertPath || '';
            this.receiveType = options.receiveType || 'application/json';
            this.sendType = options.sendType || 'application/json';
            // this.helper = WebSocketServerHelper.createServer(                                                  
            //    this, this.hostInterface, this.sslTls, this.pfxKeyCertPassword, this.pfxKeyCertPath,            
            //    this.port, this.receiveType, this.sendType                                                      
            //);                                                                                                  
        };
        util.inherits(exports.Server, EventEmitter);
    • Node: webSocketServer.start(): localhost 8087
      • The above comes from accessors/web/hosts/hosts/node/node_modules/@accessors-modules//web-socket-server/web-socket-server.js:
        exports.Server.prototype.start = function () {
            console.log("webSocketServer.start(): " + this.hostInterface + " " + this.port);

            // The ws module starts the server when you create an instance of ws.Server,                          
            // so this needs to be done here rather than in the constructor or we will                            
            // start the server prematurely.                                                                      
            this.helper = new ws.Server({ 'host': this.hostInterface,
                                          'port': this.port
                                        });
            // Register handlers. Note that in Node, this start function will be                                  
            // completed before any of these handlers is invoked, so there is no race                              
            // condition caused by the fact that we already issued the command above                              
            // to start the server.                                                                                
            this.helper.on('listening', function() {
                console.log("webSocketServer.start(): listening event");
            });
    • Node: webSocketServer.start(): listening event
      • See above.

Analysis

From the above:

  • connect() is fine:
    • Cape Code: WebSocketClient: WebSocketClient.js: connect() (Thread[WebSocketClientJS,1,main])
    • Node: WebSocketClient.js: connect()
      • Further analysis indicates that connect() in accessor/accessors/web/net/WebSocketClient.js is returning because this.get('port') is returning -1:
        exports.connect = function () {
            console.log("WebSocketClient.js: connect()");
            // Note that if 'server' and 'port' both receive new data in the same                                  
            // reaction, then this will be invoked twice. But we only want to open                                
            // the socket once.  This is fairly tricky.                                                            

            var portValue = this.get('port');
            if (portValue < 0) {
                // No port is specified. This could be a signal to close a previously                              
                // open socket.                                                                                    
                if (client) {
                    client.close();
                }
                previousPort = null;
                previousServer = null;
                console.log("WebSocketClient.js: connect(): portValue: " + portValue +
                            ", which is less than 0. This could be a signla to close a previously open socket." +
                            "  Returning.");
                return;
            }

Solutions

Continuing with Node

One possible solution is to wait a bit and then try to connect again.

However, that is not working.

accessors/web/hosts/hosts/node/node_modules/@accessors-modules//web-socket-client/web-socket-client.js was edited so that there is a new method called connectWebsocket:

/** Open the socket connection. Call this after setting up event handlers. */
exports.Client.prototype.open = function () {
    console.log("webSocketClient.open(): this.host: " + this.host);
    // The code below is from the Cape Code module.                                                        
    // this.helper.open();                                                                                

    // There is no ws open(). We wait to instantiate the WebSocket                                        
    // until we are here because we want to be able to register                                            
    // handlers before we open the WebSocket connection.                                                  

    // Strictly speaking, it is not necessary in Node to this, but it is in Vert.x.                        

    // Before this method is called, the client code will register an                                      
    // open handler (for example) or a message handler.                                                    
    this.connectWebsocket();
};
// FIXME: this should probably not be exported.                                                            
exports.Client.prototype.connectWebsocket = function () {
    console.log("webSocketClient.connectWebsocket()");
    var protocol = 'http';
    if (this.sslTls) {
        protocol = 'https';
    }
    var url = protocol + "://" + this.host + ":" + this.port;
    try {
        this.helper = new WebSocket(url);
    } catch (err) {
        throw new Error("Failed to instantiate a Websocket host, url was '" + url + "': " + err);
    }
    this.helper.on('close', function() {
        console.log("webSocketClient.open(): close event");
        this.emit('close');
    });

    var self = this;
    this.helper.on('error', function(message) {
        console.log("webSocketClient.open(): error event: " + message + " timeBetweenRetries: " + self.tim\
eBetweenRetries);

        // It could be that the WebSocketServer is not yet present.                                        
        // See ptolemy/actor/lib/jjs/modules/webSocket/WebSocketHelper.java                                

        // FIXME: the body of this setTimeout() does not get invoked?                                      
        setTimeout(function() {
            //console.log("webSocketClient.open(): error event: about to call connectWebsocket()");        
            self.connectWebsocket();
        }, self.timeBetweenRetries);

        // However, if I uncomment the definition of retryConnection()                                    
        // above and the line below, then that method gets invoked.  
        // self.retryConnection();                                                                        

        this.emit('error', message);
    });

    this.helper.on('message', function(body) {
        console.log("webSocketClient.open(): message event:" + body);
        this.emit('message', body);
    });

    this.helper.on('open', function() {
        console.log("webSocketClient.open(): open event");
        this.emit('open');
    });
};

When I run under node 6.2.0:

bash-3.2$ node --version
v6.2.0
bash-3.2$ (cd $PTII/org/terraswarm/accessor/accessors/web/net/test/auto; node ../../../{$HOSTS_NODE}/nodeHost\
Invoke.js -timeout 3000 net/test/auto/WebSocketClientJS)
Reading accessor at: /Users/cxh/ptII/org/terraswarm/accessor/accessors/web/net/test/auto/WebSocketClientJS\
.js
Reading accessor at: /Users/cxh/ptII/org/terraswarm/accessor/accessors/web/net/WebSocketClient.js
Reading accessor at: /Users/cxh/ptII/org/terraswarm/accessor/accessors/web/net/WebSocketServer.js
Reading accessor at: /Users/cxh/ptII/org/terraswarm/accessor/accessors/web/test/TrainableTest.js
Instantiated accessor WebSocketClientJS with class net/test/auto/WebSocketClientJS
WebSocketClient.js: initialize()
WebSocketClient.js: connect()
WebSocketClient.js: connect() calling new WebSocket.Client()
webSocketClient.Client()
webSocketClient.open(): this.host: localhost
webSocketClient.connectWebsocket()
WebSocketClient.js: connect() done
webSocketServer.Server()
webSocketServer.start(): localhost 8087
webSocketServer.start(): listening event
webSocketClient.open(): error event: Error: connect ECONNREFUSED 127.0.0.1:8087 timeBetweenRetries: 1000
webSocketClient.open(): error event: Error: connect ECONNREFUSED 127.0.0.1:8087 timeBetweenRetries: 1000
webSocketClient.open(): error event: Error: connect ECONNREFUSED 127.0.0.1:8087 timeBetweenRetries: 1000
webSocketClient.open(): error event: Error: connect ECONNREFUSED 127.0.0.1:8087 timeBetweenRetries: 1000
webSocketClient.open(): error event: Error: connect ECONNREFUSED 127.0.0.1:8087 timeBetweenRetries: 1000
webSocketClient.open(): error event: Error: connect ECONNREFUSED 127.0.0.1:8087 timeBetweenRetries: 1000
webSocketClient.open(): error event: Error: connect ECONNREFUSED 127.0.0.1:8087 timeBetweenRetries: 1000
webSocketClient.open(): error event: Error: connect ECONNREFUSED 127.0.0.1:8087 timeBetweenRetries: 1000
webSocketClient.open(): error event: Error:bash-3.2$

Using the debugger indicates that the issue is the stack size:

WebSocketClient.js: connect() done
webSocketServer.Server()
webSocketServer.start(): localhost 8087
webSocketServer.start(): listening event
webSocketClient.open(): error event: Error: connect ECONNREFUSED 127.0.0.1:8087 timeBetweenRetries: 1000
webSocketClient.open(): error event: Error: connect ECONNREFUSED 127.0.0.1:8087 timeBetweenRetries: 1000
webSocketClient.open(): error event: Error: connect ECONNREFUSED 127node.js:257
      get: function() {
                   ^

RangeError: Maximum call stack size exceeded
    at Object.defineProperty.get (node.js:257:20)
    at WebSocket.<anonymous> (/Users/cxh/ptII/org/terraswarm/accessor/accessors/web/{$HOSTS_NODE}/node_module\
s/webSocketClient/webSocketClient.js:232:9)
    at emitOne (events.js:96:13)
    at WebSocket.emit (events.js:188:7)
    at WebSocket.<anonymous> (/Users/cxh/ptII/org/terraswarm/accessor/accessors/web/{$HOSTS_NODE}/node_module\
s/webSocketClient/webSocketClient.js:248:14)
    at emitOne (events.js:96:13)
    at WebSocket.emit (events.js:188:7)
    at WebSocket.<anonymous> (/Users/cxh/ptII/org/terraswarm/accessor/accessors/web/{$HOSTS_NODE}/node_module\
s/webSocketClient/webSocketClient.js:248:14)
    at emitOne (events.js:96:13)
    at WebSocket.emit (events.js:188:7)
[Inferior 1 (process 11441) exited with code 01]

Adding the following method:

exports.Client.prototype.retryConnection = function () {
    console.log("webSocketClient.retryConnection()");
    // It could be that the WebSocketServer is not yet present.                                            
    // See ptolemy/actor/lib/jjs/modules/webSocket/WebSocketHelper.java                                    
    setTimeout(this.connectWebsocket, 1000);
};
(:source:)
and changing the contents of @@connectWebsocket()@@: (:source lang=javascript:)
    this.helper.on('error', function(message) {
        console.log("webSocketClient.open(): error event: " + message + " timeBetweenRetries: " + self.tim\
eBetweenRetries);

        // It could be that the WebSocketServer is not yet present.                                        
        // See ptolemy/actor/lib/jjs/modules/webSocket/WebSocketHelper.java                                

        // FIXME: the body of this setTimeout() does not get invoked?                                      
        // setTimeout(function() {                                                                        
        //     //console.log("webSocketClient.open(): error event: about to call connectWebsocket()");    
        //     self.connectWebsocket();                                                                    
        // }, self.timeBetweenRetries);                                                                    

        // However, if I uncomment the definition of retryConnection()                                    
        // above and the line below, then that method gets invoked.                                        

        self.retryConnection();

        this.emit('error', message);
    });

results in this from within ggdb after break exit:

(gdb) break exit
Breakpoint 1 at 0x7fff85f3973c
(gdb) r ../../../{$HOSTS_NODE}/nodeHostInvoke.js -timeout 5000 net/test/auto/WebSocketClientJS
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /usr/local/bin/node ../../../{$HOSTS_NODE}/nodeHostInvoke.js -timeout 5000 net/test/auto/We\
bSocketClientJS
Reading accessor at: /Users/cxh/ptII/org/terraswarm/accessor/accessors/web/net/test/auto/WebSocketClientJS\
.js
Reading accessor at: /Users/cxh/ptII/org/terraswarm/accessor/accessors/web/net/WebSocketClient.js
Reading accessor at: /Users/cxh/ptII/org/terraswarm/accessor/accessors/web/net/WebSocketServer.js
Reading accessor at: /Users/cxh/ptII/org/terraswarm/accessor/accessors/web/test/TrainableTest.js
Instantiated accessor WebSocketClientJS with class net/test/auto/WebSocketClientJS
WebSocketClient.js: initialize()
WebSocketClient.js: connect()
WebSocketClient.js: connect() calling new WebSocket.Client()
webSocketClient.Client()
webSocketClient.open(): this.host: localhost
webSocketClient.connectWebsocket()
WebSocketClient.js: connect() done
webSocketServer.Server()
webSocketServer.start(): localhost 8087
webSocketServer.start(): listening event
webSocketClient.open(): error event: Error: connect ECONNREFUSED 127.0.0.1:8087 timeBetweenRetries: 1000
webSocketClient.retryConnection()
webSocketClient.open(): error event: Error: connect ECONNREFUSED 127.0.0.1:8087 timeBetweenRetries: 1000
webSocketClient.retryConnection()
node.js:257
      get: function() {
                   ^

RangeError: Maximum call stack size exceeded
    at Object.defineProperty.get (node.js:257:20)
    at WebSocket.<anonymous> (/Users/cxh/ptII/org/terraswarm/accessor/accessors/web/{$HOSTS_NODE}/node_module\
s/webSocketClient/webSocketClient.js:232:9)
    at emitOne (events.js:96:13)
    at WebSocket.emit (events.js:188:7)
    at WebSocket.<anonymous> (/Users/cxh/ptII/org/terraswarm/accessor/accessors/web/{$HOSTS_NODE}/node_module\
s/webSocketClient/webSocketClient.js:248:14)
    at emitOne (events.js:96:13)
    at WebSocket.emit (events.js:188:7)
    at WebSocket.<anonymous> (/Users/cxh/ptII/org/terraswarm/accessor/accessors/web/{$HOSTS_NODE}/node_module\
s/webSocketClient/webSocketClient.js:248:14)
    at emitOne (events.js:96:13)
    at WebSocket.emit (events.js:188:7)
[New Thread 0x1527 of process 11452]
[New Thread 0x1627 of process 11452]
[New Thread 0x1717 of process 11452]
[New Thread 0x1807 of process 11452]
[New Thread 0x1907 of process 11452]
[New Thread 0x1a07 of process 11452]
[New Thread 0x1b07 of process 11452]
[New Thread 0x1c17 of process 11452]
[New Thread 0x2007 of process 11452]

Breakpoint 1, 0x00007fff85f3973c in exit () from /usr/lib/system/libsystem_c.dylib
(gdb) where
#0  0x00007fff85f3973c in exit () from /usr/lib/system/libsystem_c.dylib
#1  0x00000001007e1ae5 in node::FatalException(v8::Isolate*, v8::Local<v8::Value>, v8::Local<v8::Message>)\
 ()
#2  0x0000000100546bed in v8::internal::MessageHandler::ReportMessage(v8::internal::Isolate*, v8::internal\
::MessageLocation*, v8::internal::Handle<v8::internal::JSMessageObject>) ()
#3  0x0000000100532c09 in v8::internal::Isolate::ReportPendingMessages() ()
#4  0x00000001004440e4 in v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, bool, v8::in\
ternal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handl\
e<v8::internal::Object>*, v8::internal::Handle<v8::internal::Object>) ()
#5  0x0000000100443e33 in v8::internal::Execution::Call(v8::internal::Isolate*, v8::internal::Handle<v8::i\
nternal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Obje\
ct>*) ()
#6  0x0000000100167a86 in v8::Function::Call(v8::Local<v8::Context>, v8::Local<v8::Value>, int, v8::Local<\
v8::Value>*) ()
#7  0x00000001007d3f51 in node::AsyncWrap::MakeCallback(v8::Local<v8::Function>, int, v8::Local<v8::Value>\
*) ()
#8  0x000000010081cea0 in node::TCPWrap::AfterConnect(uv_connect_s*, int) ()
#9  0x000000010091aa56 in uv.stream_io ()
#10 0x0000000100922510 in uv.io_poll ()
#11 0x00000001009135ef in uv_run ()
#12 0x00000001007e884d in node::Start(int, char**) ()
#13 0x0000000100000e34 in start ()
(gdb)

Questions:

  1. Why doesn't the setTimeout() call work inside this.helper.on('error', function(message) {. I should at least be seeing the console.log
  2. How do I stop the errors after I've called setTimeout()?

Retry

Maybe instead, we should use a Node retry module

See Also


Back to Optional JavaScript Modules

Edit - History - Print - Recent Changes - Search
Page last modified on May 18, 2016, at 01:09 am