Archived

This forum has been archived. Please start a new discussion on GitHub.

Node.js : how to handle timeout (network failure) error and reconnection?

sulliwane
edited July 2016 in Help Center

Hello,

I connect to an ICE server from my node.js program, through Glacier2 (I register callbacks).

On javascript side, I call session.refreshSession() every 1s to detect a connectivity problem:

  const refreshSession = () => {
      router.refreshSession()
      .delay(timeout.toNumber() * 20)
      .then(() => {
        refreshSession();
      }, error => {
        debug('Error refreshSession(): %o', error);
      });
    };
  refreshSession();

when I disable network (simulate network failure) after the timeout is over, refreshSession() will throw an error (see below related error):

 Error at Function.Exception.captureStackTrace (myProject/node_modules/ice/src/Ice/Exception.js:103:17) at Class.__init__ [as constructor] (myProject/node_modules/ice/src/Ice/Exception.js:127:19) at .constructor (myProject/node_modules/ice/src/Ice/LocalException.js:560:32) at new <anonymous> (myProject/node_modules/ice/src/Ice/LocalException.js:610:34) at Object.callback (myProject/node_modules/ice/src/Ice/OutgoingAsync.js:126:43) at Ice.Class.handleTimeout (myProject/node_modules/ice/src/Ice/Timer.js:100:23) at Timeout._onTimeout (myProject/node_modules/ice/src/Ice/Timer.js:42:53) at tryOnTimeout (timers.js:224:11) at Timer.listOnTimeout (timers.js:198:5)

After that, I would like to call my createSession() function again (see below),

const id = new Ice.InitializationData();
id.properties = Ice.createProperties();
id.properties.setProperty('Ice.Default.Router', glacierRouterUrl);
const communicator = Ice.initialize(process.argv, id);

const OnMdServerCallback = new Ice.Class(iceLive.MdSessionCallBack, iceCallback);

const createSession = async () => {
  try {
    router = communicator.getDefaultRouter();
    router = await Glacier2.RouterPrx.checkedCast(router);

    session = await router.createSession('user', 'password');
    session = await iceLive.MdSessionPrx.uncheckedCast(session);
    // create the client object adapter.
    const [timeout, category, adapter] = await Promise.all([
      router.getSessionTimeout(),
      router.getCategoryForClient(),
      communicator.createObjectAdapterWithRouter('', router),
    ]);

    const callback = iceLive.MdSessionCallBackPrx.uncheckedCast(
      adapter.add(new OnMdServerCallback(), new Ice.Identity('callback', category))
    );

    setCallbackReturn = await session.setCallBack(callback);
    return;
  } catch (error) {
    debug('Error createSession(): %o', error);
  }
};

but it throws a session exists error (see below):

 { Error at Function.Exception.captureStackTrace (myProject/node_modules/ice/src/Ice/Exception.js:103:17) at Class.__init__ [as constructor] (myProject/node_modules/ice/src/Ice/Exception.js:157:19) at new <anonymous> (myProject/node_modules/ice/src/Glacier2/Session.js:59:31) at Class.createUserException (myProject/node_modules/ice/src/Ice/BasicStream.js:2867:26) at Class.throwException (myProject/node_modules/ice/src/Ice/BasicStream.js:606:39) at Class.throwException (myProject/node_modules/ice/src/Ice/BasicStream.js:2597:39) at Ice.Class.__throwUserException (myProject/node_modules/ice/src/Ice/OutgoingAsync.js:508:26) at Function.ObjectPrx.__check (myProject/node_modules/ice/src/Ice/ObjectPrx.js:761:13) at Function.ObjectPrx.__completed (myProject/node_modules/ice/src/Ice/ObjectPrx.js:645:19) at myProject/node_modules/ice/src/Ice/ObjectPrx.js:615:23 at Ice.Class.__markFinished (myProject/node_modules/ice/src/Ice/AsyncResult.js:94:13) at Ice.Class.__completed (myProject/node_modules/ice/src/Ice/OutgoingAsync.js:448:18) at Class.dispatch (myProject/node_modules/ice/src/Ice/ConnectionI.js:904:31) at Class.message (myProject/node_modules/ice/src/Ice/ConnectionI.js:878:14) at ._bytesAvailableCallback (myProject/node_modules/ice/src/Ice/ConnectionI.js:162:35) at Ice.Class.socketBytesAvailable (myProject/node_modules/ice/src/Ice/TcpTransceiver.js:318:18) at Socket.<anonymous> (myProject/node_modules/ice/src/Ice/TcpTransceiver.js:75:58) at emitOne (events.js:96:13) at Socket.emit (events.js:188:7) at readableAddChunk (_stream_readable.js:172:18) at Socket.Readable.push (_stream_readable.js:130:10) at TCP.onread (net.js:542:20) reason: 'session exists', __slicedData: null }

My questions:
1. Is the sessions exist error due to some method I should not call again in createSession()?
2. What is the classic way to handle network failure?

Thank you very much for your help :smile:

Tagged:

Comments

  • xdm
    xdm La Coruña, Spain

    The error indicates that a Glacier2 session exists with the same Ice connection, you can only have one session per connection, In you case the timeout error doesn't automatically close the connection.

    It is simpler to restart the client, when there is an error you should call destroySession on the router proxy and communicator destroy, once that is done you can restart the client. That is what we do in Glacier2::Application helper but this class has not been ported to JavaScript.

    You can also use ACM heartbeat to keep the session alive instead of calling refreshSession, the JavaScript chat demo shows how to do that.

  • sulliwane
    edited July 2016

    Hi Jose, many thanks for your help.

    do you mean I should add something like communicator.destroy(); router.destroySession(); to my refreshSession() function? :

      const refreshSession = () => {
          router.refreshSession()
          .delay(timeout.toNumber() * 20)
          .then(() => {
            refreshSession();
          }, error => {
            debug('Error refreshSession(): %o', error);
            router.destroySession();
            communicator.destroy();
          });
        };
      refreshSession();
    

    Moreover, when you say I should restart the client, do you mean I should restart my node process, or is there a way to just restart ice connection? (I would prefer avoid restarting node, if I can catch the error and just re-create the connection + session).

    Again, many thanks for your help.

    note: I don't have access to the Glacier2 router, so right now, I would prefer find a solution without ACM if possible.

  • xdm
    xdm La Coruña, Spain

    You need to call router.destroySession and communicator.destroy, the calls are asynchronous and you should wait for them to complete, then call createSession.

    The call to communicator.destroy must be after router.destroySession completion whenever it succeed or fail.

    In any case you don't need to take NodeJS process down.

  • Here are the steps:

    1. I simulate a network error (disable networking module)
    2. after around 40s, my refreshSession() throw an error, and call createSession()
    3. I bring the network back
    4. createSession() await on router.destroySession():
    if (router) {
      debug('about to router.destroySession()');
      await router.destroySession();
      await communicator.destroy();
    }
    

    and here is the error I get when calling router.destroySession():

    about to router.destroySession() +0ms
    Error at Function.Exception.captureStackTrace (myProject/node_modules/ice/src/Ice/Exception.js:103:17) at Class.__init__ [as constructor] (myProject/node_modules/ice/src/Ice/Exception.js:157:19) at new <anonymous> (myProject/node_modules/ice/src/Glacier2/Router.js:60:31) at Class.createUserException (myProject/node_modules/ice/src/Ice/BasicStream.js:2867:26) at Class.throwException (myProject/node_modules/ice/src/Ice/BasicStream.js:606:39) at Class.throwException (myProject/node_modules/ice/src/Ice/BasicStream.js:2597:39) at Ice.Class.__throwUserException (myProject/node_modules/ice/src/Ice/OutgoingAsync.js:508:26) at Function.ObjectPrx.__check (myProject/node_modules/ice/src/Ice/ObjectPrx.js:761:13) at Function.ObjectPrx.__completed (myProject/node_modules/ice/src/Ice/ObjectPrx.js:645:19) at myProject/node_modules/ice/src/Ice/ObjectPrx.js:615:23 at Ice.Class.__markFinished (myProject/node_modules/ice/src/Ice/AsyncResult.js:94:13) at Ice.Class.__completed (myProject/node_modules/ice/src/Ice/OutgoingAsync.js:448:18) at Class.dispatch (myProject/node_modules/ice/src/Ice/ConnectionI.js:904:31) at Class.message (myProject/node_modules/ice/src/Ice/ConnectionI.js:878:14) at ._bytesAvailableCallback (myProject/node_modules/ice/src/Ice/ConnectionI.js:162:35) at Ice.Class.socketBytesAvailable (myProject/node_modules/ice/src/Ice/TcpTransceiver.js:318:18) at Socket.<anonymous> (myProject/node_modules/ice/src/Ice/TcpTransceiver.js:75:58) at emitOne (events.js:96:13) at Socket.emit (events.js:188:7) at readableAddChunk (_stream_readable.js:172:18) at Socket.Readable.push (_stream_readable.js:130:10) at TCP.onread (net.js:542:20) +16ms
    

    I'm not sure what I'm doing wrong...Any ideas? Thanks Jose!

  • xdm
    xdm La Coruña, Spain
    edited July 2016

    As I wrote before you need to call communicator destroy whenever destroySession succeed or fail, it is expected that it fail if the network is not reachable.

    router.destroySesion().finally(() => communicator.destroy()).finally(() => createSession());

  • sulliwane
    edited July 2016

    I see, thanks for the details.

    I was awaiting on communicator.destroy(), but seems like it's not a standard promise (?) so I put it inside a Promise:

    const communicatorDestroyPromise = () => new Promise(
      resolve => {
        communicator.destroy().finally(() => resolve());
      }
    );
    if (communicator && communicator.destroy) await communicatorDestroyPromise();
    

    One last thing: Is setting .ice_invocationTimeout(5000) the proper way to control after how long time my refreshSession() will throw an error? (see below):

    router = communicator.getDefaultRouter().ice_invocationTimeout(5000);
    

    It seems to work, as refreshSession() will correctly throw an error after 5s of network failure. But the problem is that after adding .ice_invocationTimeout(5000) I don't receive data anymore through my ice callback function.

    Mabe I'm not setting it correctly...

    Any idea on this one? (I think it's related to my question, but I can open a new topic if you prefer).

    Many thanks Jose for your precious help.

  • xdm
    xdm La Coruña, Spain

    Hi

    Unfortunately there is a bug in the outgoing connection factory that prevents ice_invocationTimeout to work correctly in this case I will push a fix shortly but meanwhile you can workaround by using Ice.Default.InvocationTimeout property

    id.properties.setProperty('Ice.Default.InvocationTimeout', '5000');
    
  • Thanks, this works as expected!