Skip to content
This repository has been archived by the owner on May 1, 2020. It is now read-only.

Auto-connect and auto-reconnect #11

Closed
KernelPryanic opened this issue Mar 20, 2018 · 5 comments
Closed

Auto-connect and auto-reconnect #11

KernelPryanic opened this issue Mar 20, 2018 · 5 comments
Assignees
Labels
client feature request (deprecated) This label is deprecated since December 10, 2018
Milestone

Comments

@KernelPryanic
Copy link
Collaborator

Make the webwire client automatically connect to the server right after initialization and make it optionally automatically reconnect on connection loss

@romshark
Copy link
Owner

romshark commented Mar 20, 2018

That's a valid point, indeed it'd be better if the client takes care of connect/reconnect to ease the use of the library, though before this feature can be implemented the following questions must be clarified:

  • A: When sending a signal when the server is being unresponsive, what shall we expect?

    • A1: blocking the calling goroutine until the connection is established and the signal is sent?
    • A2: same as A1 but timing out after a while returning a special TimeoutConnErr?
    • A3: immediately returning a DisconnectedErr?
  • B: When making a request when the server is being unresponsive, what shall we expect?

    • B1: blocking the calling goroutine until the connection is established and the request is sent timing out when the specified (or default) request timeout is reached returning a RequestTimeoutErr error?
    • B2: immediately returning a DisconnectedErr?

@romshark
Copy link
Owner

romshark commented Mar 20, 2018

Code Examples

Signals

Signals (A1) - Make Signal automatically reconnect

User code loses control, calling goroutine blocks until the server receives the signal.

Go:

// NotifyEvent will block until the server is notified
func NotifyEvent(...) {
  // Will block for as long as the server is unavailable
  err := client.Signal(...)
  switch err := err.(type) {
  case nil:
    // Signal sent, exit function
    return
  default:
    // Unexpected failure
    panic(err)
  }
}

JavaScript (ES6):

async function notifyEvent(...) {
  const {err} = await client.signal(...)
  if (err != null) {
    // Unexpected failure
    throw err
  }
  // Signal sent, exit function
}

Signals (A2) - Make Signal automatically reconnect with a timeout

User code gains control every 2 seconds until the server receives the signal. Will behave same as A1 if DefaultSignalTimeout is set to zero.

Go:

// Set during client initialization:
Options {
  DefaultSignalTimeout: 2 * time.Second,
}

// NotifyEvent will block until the server is notified
func NotifyEvent(...) {
  for {
    // Will block for as long as the server is unavailable
    err := client.Signal(...)
    switch err := err.(type) {
    case nil:
      // Signal sent, exit function
      return
    case DisconnectedErr:
      // 2 seconds reconnection timeout reached, retry sending
      continue
    default:
      // Unexpected failure
      panic(err)
    }
  }
}

JavaScript (ES6):

// Set during client initialization:
options = {
  defaultSignalTimeout: 2000,
}

async function notifyEvent(...) {
  const {err} = await client.signal(...)
  if (err == null) {
    // Signal sent, exit function
    return
  } else if (err.errType === 'disconnected') {
    // 2 seconds reconnection timeout reached, retry sending
    notifyEvent()
  } else {
    // Unexpected failure
    throw err
  }
}

Signals (A3) - Make Signal return an error immediately (current api)

This is how the API currently works, it's quite okay, though you must remember to set a sleep after a failed reconnection attempt before retrying, otherwise CPU usage is dramatically increased iterating over the loop for thousands of times without any sleeps.

Go:

// NotifyEvent will block until the post was created
func NotifyEvent(...) {
  for {
    err := client.Signal(...);
    switch err := err.(type) {
    case nil:
      // Signal sent, exit function
      return
    case DisconnectedErr:
      connErr := client.Connect()
      switch err := connErr.(type) {
      case nil:
        // Reconnected, retry sending signal
      case UnavailableErr:
        // Reconnection failed, wait for 2 seconds and try again
        time.Sleep(2 * time.Second)
      default:
        // Unexpected failure
        panic(err)
      }
      // Now retry sending when connected again
    default:
      // Unexpected failure
      panic(err)
    }
  }
}

JavaScript (ES6):

async function notifyEvent(...) {
  const {err} = await client.signal(...)
  if (err == null) {
    // Signal sent, exit function
    return
  } else if (err.errType === 'disconnected') {
    const {connErr} = await client.connect()
    if (connErr == null) {
      // Reconnected, retry sending signal
      notifyEvent()
    } else if (connErr.errType === 'unavailable') {
      // Reconnection failed, wait for 2 seconds and try again
      setTimeout(notifyEvent, 2000)
    } else {
      // Unexpected failure
      throw err
    }
  } else {
    // Unexpected failure
    throw err
  }
}

Requests (B1) - Make Request automatically reconnect

The only risk here, is that client.Request will block for as long as the server is unavailable if DefaultRequestTimeout is set to zero. One could mitigate that calling client.TimedRequest(..., 10 * time.Second) instead, then ReqTimeoutErr will be returned and the user code is back in control again.

Go:

// Set during client initialization:
Options {
  // don't ever timeout requests
  DefaultRequestTimeout: 0,
}

// CreatePost will block until the post was created
func CreatePost(...) {
  for {
    // Will block for as long as the server is unavailable
    reply, err := client.Request(...);
    switch err := err.(type) {
    case nil:
      // Request processed, exit function
      return /*reply*/
    case ConnTimeoutErr:
      // <only if DefaultRequestTimeout is bigger 0>
      // Server unavailable, retry
    case ReqTimeoutErr:
      // <only if DefaultRequestTimeout is bigger 0>
      // Server unresponsive, retry
    default:
      // Unexpected failure
      panic(err)
    }
  }
}

JavaScript (ES6):

// Set during client initialization:
options = {
  // don't ever timeout requests
  defaultRequestTimeout: 0,
}

async function createPost(...) {
  const {reply, err} = await client.request(...)
  if (err == null) {
    // Request processed, exit function
    return {reply}
  }
  // will branch off here only if DefaultRequestTimeout is bigger 0
  else if (err.errType === 'timeout') {
    // If timed out then simply retry
    createPost()
  } else {
    // Unexpected failure
    throw err
  }
}

Requests (B2) - Make Request return an error immediately (current api)

Same as with A3, you must remember to call set a sleep after a failed reconnection attempt before retrying the request, otherwise CPU usage is dramatically increased iterating over the loop for thousands of times without any sleeps.

Go:

// CreatePost will block until the post was created
func CreatePost(...) {
  for {
    reply, err := client.Request(...);
    switch err := err.(type) {
    case nil:
      // Request processed, exit function
      return /*reply*/
    case ReqTimeoutErr:
      // Retry request
      continue
    case DisconnectedErr:
      connErr := client.Connect()
      switch err := connErr.(type) {
      case nil:
        // Reconnected, retry sending signal
      case UnavailableErr:
        // Reconnection failed, wait for 2 seconds and try again
        time.Sleep(2 * time.Second)
      default:
        // Unexpected failure
        panic(err)
      }
    default:
      // Unexpected failure
      panic(err)
    }
  }
}

JavaScript (ES6):

async function createPost(...) {
  const {reply, err} = await client.request(...)
  if (err == null) {
    // Request processed, exit function
    return {reply}
  } else if (err.errType === 'disconnected') {
    const {connErr} = await client.connect()
    if (connErr == null) {
      // Reconnected, retry request
      createPost()
    } else if (connErr.errType === 'unavailable') {
      // Reconnection failed, wait for 2 seconds and try again
      setTimeout(createPost, 2000)
    } else {
      // Unexpected failure
      throw err
    }
  } else {
    // Unexpected failure
    throw err
  }
}

@romshark romshark added the undecided The solution is in discussion and yet undecided label Mar 20, 2018
@KernelPryanic
Copy link
Collaborator Author

That's how I can see this:

  • In case of Requests we can do it more reliable way by adding auto-reconnect functionality to them and use it like B1.
  • In case of Signals I would recommend to let it be as it is due the nature of signals in "sent and forgot" way. So it's A3.

@romshark
Copy link
Owner

Having though about it, I think it's worth having client.Request try to autoconnect until timeout by default. If one sets DefaultRequestTimeout to 60 seconds for example and the server is unreachable, then it will try to connect within those 60 seconds before failing with a timeout error.

This feature should be optional but enabled by default, thus having an option to disable autoconnect is desirable. If autoconnect is disabled then client.Request should return a DisconnectedErr right away if the server isn't available.

I agree that client.Signal on the other hand should not support autoconnect, but it still should try to create a connection if there's none. If it fails to connect then it should return DisconnectedErr right away.

@romshark romshark removed the undecided The solution is in discussion and yet undecided label Mar 21, 2018
@romshark romshark added this to the v1.0.0 rc1 milestone Mar 21, 2018
@romshark romshark self-assigned this Mar 21, 2018
@romshark
Copy link
Owner

As of commit #e996bb6 the autoconnect feature is fully implemented and tested!

client.Request, client.TimedRequest, client.RestoreSession and client.Signal API methods will now automatically try (at least once) to connect to the server if there's no active connection and restore the session if necessary.

If Options.Autoconnect is enabled (which it is by default) then client.Request, client.TimedRequest and client.RestoreSession will try to automatically establish a connection to the server, even if the server is unavailable, for as long as the timeout isn't triggered, rather than returning a DisconnectedErr right away.

client.Signal is the only exception to autoconnect and will return a DisconnectedErr immediately if the server is unavailable.

Manually calling client.Connect is no longer required but remains available in the client API for special use cases, such as when one would want to test server connectivity and similar.

@romshark romshark added client feature request (deprecated) This label is deprecated since December 10, 2018 and removed feature request labels Aug 7, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
client feature request (deprecated) This label is deprecated since December 10, 2018
Projects
None yet
Development

No branches or pull requests

2 participants