Skip to content

Offline application #1

@flowersinthesand

Description

@flowersinthesand

See a summary of discussion about this feature

The current design works well with opened socket, online. However, in the real world, it is necessary to consider when connection is closed temporarily, offline. To put it concretely, there must be a way for server or client to send events again which couldn't be sent in time due to temporal disconnection.

The basic idea of this feature is to change the relationship of socket and transport from one-to-one to one-to-many.

Now that transport concept is introduced, an object called socket is not one-time connectivity any more. Therefore, even in server like client, socket reference should be maintained regardless of connection and disconnection and only underlying transport should be replaced internally according to connection and disconnection. For this, socket should have a unique identifier and underlying transports should share that identifier A. Then, when connection is closed temporarily, both server-side and client-side socket can accumulate events which user tries to send to somewhere.

Some events should be resent but some events should be not. For example, assume that live video streaming is done by using binary event. Because it is live, end-user may want to skip frames missed while disconnection. (The server may want to skip missing frames for memory issue as well) In that case, such events should be not cached and resent on reopen. Therefore, application developer should have a flexible way to decide whether or not to cache events that couldn't be sent and send them on socket's reopen B.

Also to tell the permanent close from the temporal close, a kind of timeout is required. It may be not needed in client but cause some memory issue in server. It draws a new state, destroyed, which is an end of life cycle of socket. In other means, if no new transport is established in the timeout e.g. 30s, the corresponding socket regarded as temporally closed should be regarded as permanently closed, C. A temporally closed socket can deal with events which couldn't be sent but a permanently closed socket can't be and shouldn't be used at all any more.

A, B and C are sub tasks of this feature. Any details about sub tasks are not clear and determined yet and should have a more discussion. Especially, more definite definition about socket's life cycle on both side is required. Also terms like open and close and online and offline should be used by definition.

  • A - Extended life cycle of socket.
    • A transport whose URI doesn't have a socket id should be issued a socket id in handshaking the protocol. Then, client-side socket's state transitions from connecting to opened state and server-side socket is constructed.
    • A transport whose URI have a socket id should be injected to the corresponding socket. Then, both client-side and server-side socket's state transitions from closed to opened state.
    • Especially in browser due to page navigation, socket reference can't be maintained. A cookie can be used but is it enough? any side effect?
    • Consider how temporally closed socket and persistently closed socket should reconnect in client.
  • B - How to deal with send method on offline.
    • By introducing new event. This way is used in the below code snippet.
    • By using existing error event with new exception.
    • By throwing a new exception if sending event on offline.
  • C - Temporal close and permanent close.
    • The current closed state corresponds to temporal close and new state and event to determine permanent close is needed.

Let's take a look at new API for this feature briefly. Of course, none of API is determined.

Java Server

// When a socket is constructed,
server.onsocket(socket -> {
    // Here, application can deal with authentication and authorization
    // and tag socket with the result e.g. socket.tag("/account/" + auth.username())

    // A queue containing events the server couldn't send while disconnection
    Queue<Object[]> cache = new ConcurrentLinkedQueue<>();
    // When new transport is injected and socket is reopened,
    socket.onreopen(() -> { // or socket.ontransport?
        // Empties the queue by sending cached event one by one
        // TODO may cause an infinite loop and concurrent issue
        while(!cache.isEmpty()) {
            // Beside args, other metadata related to method like timestamp call may be required
            // So Object[] is not good signature to handle this case
            Object[] args = cache.poll();
            socket.send(args[0], args[1], args[2], args[3]);
        }
    });
    // When an event couldn't be sent since it's offline
    socket.oncouldntsend(args -> {
        // You can filter args to cache or not
        cache.offer(args);
    });

    // ...
    // Now server-side socket has repeated life cycle
    // It should have a method to determine the current state
    socket.state() // [OPENED, CLOSED], DESTROYED

    // After this event, socket can't be and shouldn't be used any more
    socket.ondestroy(() -> {
        // This event is a good place to release other resources 
        // depending on this socket or sharing the life cycle of this socket
        cache.clear();
    });
});

// ...
// Now server's selector methods execute a given action for opened and closed socket
// A socket closed more than 30s are automatically destroyed and evicted from the server
server.all(socket -> {
    // If this socket is in opened, an event will be sent through the underlying connection
    // If this socket is in closed, an event will be passed to couldntsend event as event data
    socket.send("event", data);
});

JavaScript Client

var socket = cettia.open(uri, {
    // Only for browser. This cookie is to share socket's id between page navigation
    // It may cause other confusion and I'm not sure it will work as expected
    cookieName: "cettia"
});

// A queue containing events the client couldn't send while disconnection
var cache = [];
// When a transport establishes a connection,
socket.on("open", () => {
    // Empties the cache by sending cached event one by one
    // TODO may cause an infinite loop
    while(cache.length) {
        // For the same reason with the sever, args should be not Arguments object
        var args = cache.shift();
        socket.send.apply(socket, args);
    }
});
// When an event couldn't be sent since it's offline
socket.on("couldntsend", args => {
    // A chance to determine if args should be resent or not
    cache.push(args);
});

// destroyed state is added but when should socket be in that state in client?
// The current reconnection mechanism might be affected
socket.state() // [connecting, opened, closed], destroyed

// For the same reason with the server
socket.on("destory", () => {
    // Clear the cache
    cache.length = 0;
});

// Now it will work even on offline
// On offline, it will be passed to couldntsend event
socket.send("x", y);

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions