-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Offline application #1
Comments
As for C, differentiation between temporal close and permanent close makes work much harder especially in client. (In server, it is needed to avoid memory leak though) Instead, introducing var socket = cettia.open(uri, {
// Only for browser. This cookie is to share socket's id between page navigation
// That means events which server couldn't send due to page navigation can be retrieved in the last page
cookieName: "cettia"
});
// A queue containing events the client couldn't send while disconnection
// TODO not maintained during page navigation
var cache = [];
// When a connection is tried,
// it replaces waiting event as well waiting state
socket.on("connecting", (delay, attempts) => {
// delay and attempts are (re)connection delay and (re)connection attempts respectively
});
// When a new socket id is issued,
// it happens if a socket id used to establish a connection don't exist or is invalid e.g. destroyed socket in the server
// The open event always follows this event
socket.on("reset", () => {
// Resets objects which have been for the older socket
cache.length = 0;
});
// When a transport establishes a connection,
socket.on("open", () => {
// Empties the cache by sending cached event one by one
while(cache.length) {
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);
});
// If the state is not opened, it will be passed to couldntsend event
socket.send("x", y); |
As for B, the best way to cache event when there is no connection temporally in both client and server is to utilize yet another reserved event. Here is the reason.
The code snippet will look like the following. // cache event is called if socket.state method returns CLOSED
// It's not an error but an expected case
socket.oncache((Object[] args) -> {
if (/* decides whether to cache args or not*/) {
cache.add(args);
}
});
// Or by checking out socket's state manually
if (socket.state() == Socket.State.OPENED) {
socket.send(...);
} else {
cache.add(...); // or don't cache it
}
socket.onerror(t -> {
// This case is distinguished from cache event as it tells some event couldn't be sent although there was an active connection
// For example, if some message's size is over the limit, it would happen and disconnect the existing connection as well
// In case of Java, cettia-java-platform doesn't provide normalized exception hierarchy now
// so that there is no such CouldntSendException and user should use their platform-specific exception
if (t instanceof CouldntSendException) {
// This approach is likely to be not possible
/*
Object[] args = t.args();
if (decides whether to cache args or not) {
cache.add(args);
}
*/
}
}); |
Summary of discussion about this feature - This feature provides events user can utilize to deal with sockets whose connection is disconnected for a little while properly by making a socket to be backed by multiple transports not just one.
Server example Life cycle
// When a server creates a new socket
server.onsocket(socket -> {
// Here, application can deal with authentication and authorization
// A queue containing events the server couldn't send while disconnection
Queue<Object[]> cache = new ConcurrentLinkedQueue<>();
// When a new transport is injected and a socket is (re)opened,
socket.onopen(() -> {
// Determines the socket's state to avoid infinite loop
// This state can be one of OPENED, CLOSED and DELETED
while(socket.state() == State.OPENED && !cache.isEmpty()) {
// Empties the queue by sending cached event one by one
Object[] args = cache.poll();
socket.send((String) args[0], args[1], (Action<?>) args[2], (Action<?>) args[3]);
}
});
// If some event is sent when there is no connection
socket.oncache(args -> {
// A chance to determine if args should be resent or not
cache.offer(args);
});
// After this event, socket can't be and shouldn't be used any more
socket.ondelete(() -> {
// Here is a good place to release resources having depended the socket
cache.clear();
});
});
// Now server's selector methods execute a given action for both opened and closed sockets
// A socket closed for a long time e.g. 10 m are automatically deleted and evicted from the server
server.all(socket -> {
// If this socket is opened, an event will be sent through the underlying connection
// If this socket is closed, an event will be passed to cache event as event data
socket.send("event", data);
}); Client example Life cycle
var socket = cettia.open(uri, {
// Only for browser. This option deals with cookie to share socket's id between page navigation
// That means events which server couldn't send due to page navigation can be retrieved in the last page
cookieName: "cettia"
});
// A queue containing events the client couldn't send while disconnection
// It might be needed to maintain this queue between page navigation e.g. by using JSON serialization and sessionStorage
var cache = [];
// If a connection is tried,
socket.on("connecting", () => {});
// When a new socket id is issued,
// This is the beginning of the life cycle and the end of the previous life cycle
socket.on("new", () => {
// Resets resources having been used for the older socket
cache.length = 0;
// The open event always follows this event but not vice versa
});
// When a transport establishes a connection,
socket.on("open", () => {
// Determines the socket's state to avoid infinite loop
while(socket.state() === "opened" && cache.length) {
// Empties the cache by sending cached event one by one
var args = cache.shift();
socket.send.apply(socket, args);
}
});
// If a reconnection is scheduled after disconnection,
// delay and attempts are reconnection delay calculated by reconnect option and the total number of reconnection attempts respectively
socket.on("waiting", (delay, attempts) => {});
// If some event is sent when there is no connection
socket.on("cache", args => {
// A chance to determine if args should be resent or not
cache.push(args);
});
// If the state is not opened, it will be passed to cache event
socket.send("event", data); Derivative features
How to implement is skipped as it is intuitive. |
This feature has landed. Even though multiple commits are involved to this feature, there is no change in the protocol. |
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.
Let's take a look at new API for this feature briefly. Of course, none of API is determined.
Java Server
JavaScript Client
The text was updated successfully, but these errors were encountered: