antirez / load81

SDL based Lua programming environment for kids similar to Codea
BSD 2-Clause "Simplified" License
599 stars 62 forks source link

Cloud messages for Load81 #32

Open antirez opened 12 years ago

antirez commented 12 years ago

Load81 programs are currently self-contained into a box without the ability to talk with the external world. Specifically they can't talk with other instances of the same program running in other Load81 instances around the world! What a shame. So what I'm proposing and implementing is a Cloud message service for Load81.

API

A Load81 program listen to channels using the listen() function. Multiple calls to listen are allowed in order to listen to multiple channels, and it is also possible to no longer listen to a given channel using unlisten(). Example:

listen("asteroids")
listen("foo","bar")
unlisten("asteroids")

Every time a message is received the function receive is called. It is important to understand how this works. The normal flow of a Load81 program is to call setup the first time, and then call draw. When the program is listening to one or more channels, the function receive is called before the function draw is called, and is called for all the pending messages currently in the queue, so for instance you may have 5 calls to receive, and finally the call to draw, and so forth.

This is how the function receive is defined in a program:

function receive(sender,channel,msg)
    ....
end

The three arguments are:

You don't receive messages that are sent by your instance.

In order to send messages you simply use:

send("channel",lua_object)

You can send messages to channels you are not listening without problems if you wish.

Implementation

The implementation using a Redis instance running at pubsub.load81.com, with only PUBLISH and SUBSCRIBE commands enabled. The instance is configured to drop clients that reached an output buffer of a few hundred kbytes so that it is not possible to abuse the instance just listening to channels without actually reading data.

Every Load81 instance creates a random identifier at startup, either reading from /dev/urandom or using a mix of time and rand() if urandom is not available.

Messages are send to Redis as <Instance ID>:<MessagePack representation of the Lua object sent>.

Messages are both sent and received using a non blocking socket. At every iteration we read what's new in the socket, and call a function that will put all the entire messages in the queue, parsing the Redis protocol.

From the point of view of sending, we'll try to send messages every time the user calls the send() function in order to minimize latency, but if there is no room on the kernel socket buffer we'll bufferize the write and send it when possible (at the next iteration of the event processor, that is, by default, 30 times per second).

Abuses

From the outside attacker this is a free message hub, but I think it's fine since for instance an IRC server has the same problem.... and I think no one will try to abuse this stuff, at least I hop.

agladysh commented 12 years ago

From the outside attacker this is a free message hub, but I think it's fine since for instance an IRC server has the same problem.... and I think no one will try to abuse this stuff, at least I hop.

IRC is easier, of course (and is actively abused).

But, I believe, that you should plan for this anyway. This is a perfect way for some random offended person to shut down pubsub.load81.com — since you'll have no means to ban abusive clients:

antirez commented 12 years ago

Yes... but I don't really thing there is an alternative if we want to maintain such a simple API and low barrier to entry... to the messaging party :) We'll try let's see what happens.

agladysh commented 12 years ago

API key + a service to get it via e-mail?

seclorum commented 12 years ago

I like this a lot, except for one thing:

When the program is listening to one or more channels, the function receive is called before the function draw is called, and is called for all the pending messages currently in the queue, so for instance you may have 5 calls to receive, and finally the call to draw, and so forth.

This has the potential that I could post messages to a channel and fill everyones receive() queue, degrading performance immensely before a draw() is issued - 'hanging' client programs and pissing the game-players off. Would it not be better to make receive()/draw() synchronous? I'm not sure I'm thinking about this properly, but it seems to me that having the requirement be that the queue be emptied between draw()'s would degrade FPS somehow - not good for games, even the simple variety.

Then again, maybe another option would be to have two kinds of message types: "replace-upon-insert", and "stack", although maybe this is also just asking for trouble ..

cobrajs commented 12 years ago

Maybe have receive() run before draw(), and it processes as many messages as it can before the next draw() call, so that it wouldn't degrade FPS but it could handle the messages when the load is lower?

Something like: do recieve() until drawNeeded draw()