I've been using a variant of this IPC system since about 2009 (as part
of the nginx http push module, now Nchan), so it's rather well
time-tested already. As I recall, it took about a week of digging
through the Nginx guts to figure out a proper IPC mechanism. (I used the
master->worker signal forwarding system as a template to build from.)
One small correction - the sockets are not in shared memory, but in
copy-on-write memory inherited from the master process. worker PID and
slot info is what's in shared memory. (This made reloading logic a bit
simpler)
There are 3 minor catches so far: the alert name is limited to 64K, the
data to 4T, and the alert handler runs in an ngx.timer context. That's
about all.
Russell -- incidentally, I'd be interested to see your code for your IPC
system.
Russell Sullivan:
> Hi Leo,
>
> You got the architecture right: non-blocking Unix Domain Sockets, worker to
> socket mapping in SharedMemory, and the receive-coroutine is correct too.
>
> My implementation did the first two, but the receive logic was done by
> hacking the nginx core and using nginx's internals, it was a hack.
>
> I did not know there was an easy way to add a listening socket to nginx,
> but it looks like you found one :) I did a lot of ugly stuff, only took 2
> days, and it shows :)
>
> The reason I need this is I shard requests to sticky workers. The simplest
> analogy is all requests for users [A-H] go to worker0, users [I-P] go to
> worker1, users[Q-Z] go to worker3. I do it for data linearizability using a
> LMDB backend, LMDB runs in SharedMemory but I do multiple key updates that
> need to be serialized, so I pushed that requirement into the request
> receive layer.
>
> Your implementation looks cleaner than mine, so I will probably end up
> using it if you maintain it.
>
> Are there any gotchas? It doesn't look like it, the architecture is done
> correctly.
>
> Again, very nice work :)
>
> - Russ
>
> BTW: nice writeup with the numbered links, helps understand the code and
> implementation
>
>
> On Monday, March 6, 2017 at 6:47:49 PM UTC-8, Leo Ponomarev wrote:
>>
>> Here's how it works:
>>
>> Let's say we have n workers. At master process initialization
>> (postconfig), n (unix) sockets are created with the same logic that
>> initializes worker slots [1]. Then, when each worker starts, it adds a
>> listen event on the socket in its worker slot to the nginx event tree
>> [2], and stores its process id associated with the worker slot in shared
>> memory [3].
>>
>> Then, to send an ipc alert to a target worker by process id, look up its
>> slot in shared memory, then send data to the associated socket [4]. Add
>> some send [5] and receive [6] buffering, and voila.
>>
>> Importing the data into a valid Lua thread was a little tricky. When a
>> user calls ipc.receive() in the config, the module starts a (very long)
>> timer with a unique timeout using ngx.timer.at() [7]. This timer is then
>> yanked off the timer tree (by finding it using the unique timeout) [8],
>> and registered with the lua ipc module. Then, when an alert arrives [9],
>> this timer event is deleted from the timer tree, and re-inserted with
>> timeout 0 [10]. Once the incoming events are processed, the timer
>> re-inserts itself [11] with a unique timeout again, and the process
>> repeats.
>>
>> [1] https://github.com/slact/ngx_lua_ipc/blob/master/src/ipc.c#L49-L115
>> [2] https://github.com/slact/ngx_lua_ipc/blob/master/src/ipc.c#L229-L269
>> [3]
>>
>> https://github.com/slact/ngx_lua_ipc/blob/master/src/ngx_lua_ipc.c#L385-L394
>> [4]
>>
>> https://github.com/slact/ngx_lua_ipc/blob/master/src/ngx_lua_ipc.c#L239-L254
>> [5] https://github.com/slact/ngx_lua_ipc/blob/master/src/ipc.c#L186-L227
>> [6] https://github.com/slact/ngx_lua_ipc/blob/master/src/ipc.c#L271-L477
>> [7]
>>
>> https://github.com/slact/ngx_lua_ipc/blob/master/src/lua/register_receive_handler.lua
>> [8]
>>
>> https://github.com/slact/ngx_lua_ipc/blob/master/src/ngx_lua_ipc.c#L194-L237
>> [9]
>>
>> https://github.com/slact/ngx_lua_ipc/blob/master/src/ngx_lua_ipc.c#L269-L327
>> [10]
>>
>> https://github.com/slact/ngx_lua_ipc/blob/master/src/ngx_lua_ipc.c#L313-L317
>> [11]
>>
>> https://github.com/slact/ngx_lua_ipc/blob/master/src/lua/register_receive_handler.lua#L19-L20
>>
>>
>>
>>
>> Russell Sullivan:
>>>
>>> This is very useful. I did an IPC module for nginx workers last year but
>>> have not been maintaining it very well.
>>>
>>> Can you tell us more about how you implemented it. How does IPC work
>> under
>>> the hood (TCP,UDP,SM)? I had to make some changes to the nginx core for
>>> mine to work, you didnt, now I am very interested in the internals.
>>>
>>> Nice work :)
>>>
>>> On Saturday, March 4, 2017 at 9:16:28 PM UTC-8, Leo Ponomarev wrote:
>>>>
>>>> Did a few benchmarks, the results are really good for high concurrency:
>>>>
>>>> Test case: Broadcasting from 1 worker, followed by many K rounds of
>>>> back-and-forth communication between the initial broadcaster and all
>> the
>>>> other workers. Done on an elderly i5-2450M 2.50GHz (2 core, 4 thread)
>>>> CPU. Nginx compiled with -O3.
>>>>
>>>> Time to do 50K round-trips between broadcaster and all workers (100K
>> ipc
>>>> alerts total):
>>>>
>>>> 4 workers: 18.32 sec
>>>> 10 workers: 6.15 sec
>>>> 20 workers: 3.00 sec
>>>> 30 workers: 1.92 sec
>>>> 40 workers: 1.49 sec
>>>> 50 workers: 1.22 sec
>>>> 60 workers: 1.05 sec
>>>> 70 workers: 0.94 sec
>>>> 100 workers: 0.83 sec
>>>> 150 workers: 0.81 sec
>>>>
>>>> So at crazy concurrency, it can do 120K alerts/sec on this machine.
>>>>
>>>> Robert Paprocki:
>>>>> On Fri, Mar 3, 2017 at 12:27 PM, Leo Ponomarev <l...@nchan.io
>>>> <javascript:>> wrote:
>>>>>
>>>>>> Interprocess communication for lua_nginx_module and Openresty. Send
>>>>>> named alerts with string data between Nginx worker processes.
>>>>>> Asynchronous, nonblocking, non-locking.
>>>>>>
>>>>>> Very simple to use:
>>>>>>
>>>>>> local ipc = require "ngx.ipc"
>>>>>>
>>>>>> ipc.send(destination_worker_pid, ipc_alert_name, data_string)
>>>>>>
>>>>>> ipc.receive(ipc_alert_name, function(data)
>>>>>> --ipc receiver function for all alerts with string name
>>>> ipc_alert_name
>>>>>> print("received alert from " .. ipc.sender)
>>>>>> ipc.reply("you said " .. data)
>>>>>> end)
>>>>>>
>>>>>>
>>>>>> https://github.com/slact/ngx_lua_ipc
>>>>>
>>>>>
>>>>> Way cool! Have you done any stress testing? How does this hold up in
>>>>> high-concurrency environments?
>>>>>
>>>>
>>>>
>>
>>