Posts: 783
Threads: 12
Joined: Jan 2014
Thanks: 2
Given 73 thank(s) in 61 post(s)
Web plugins do not currently have a safe way of calling the world from web threads. I propose a new API, cWorld:DoWithWorld, that allows a web thread to call functions that access the world.
Like the rest of the DoWithXYZ functions it would take a function of what to do with the world. When called DoWithWorld would try and gain the world lock with a short timeout. If this timeout where to fail, then the binding uses lua co-routines to yield control back out to the web-admin bindings. The web-admin bindings would then release the plugin lock, yield the OS thread, then regain the plugin lock, and resume back into DoWithWorld. This allows the thread holding the World lock to gain the plugin lock, without risking safety.
This would have to be documented as potentially calling back into the plugin before calling the callback, but would be safe from deadlocks. Does anyone think this would be worth blocking the use of coroutines in lua for?
Posts: 783
Threads: 12
Joined: Jan 2014
Thanks: 2
Given 73 thank(s) in 61 post(s)
A similar technique could be used to implement a safe version of the cRoot::ForEachPlayer() API, which core currently has a risk of deadlocking on.
Posts: 6,485
Threads: 176
Joined: Jan 2012
Thanks: 131
Given 1075 thank(s) in 852 post(s)
Sounds interesting, but I remember reading something about coroutines not being yieldable from within the C API; I might remember wrong, though.
Also, if you coroutine.yield, you'l need to coroutine.resume at some point, who's gonna do that?
I was considering doing asynchronous web requests instead - a webadmin request would create a class object representing it (together with the response) that would be passed to Lua plugins; plugins could service the request immediately or postpone it, storing the object for later (perhaps doing a QueueTask() etc. ) and fulfill the rest of the request later on. This would be perfect for the Gallery previews - they generate asynchronously so right now when the area is previewed for the first time, there is no image to send and a dummy must be sent instead.
Posts: 4,634
Threads: 115
Joined: Dec 2011
Thanks: 695
Given 495 thank(s) in 424 post(s)
Interesting, I had the exact same idea yesterday but then for cPlayer and cWorld:ScheduleTask. It would allow you to schedule a task for a player without first getting the world the player is in, schedule a task in it, and then use cRoot:FindAndDoWithPlayer to do an action with a cPlayer object.
Posts: 783
Threads: 12
Joined: Jan 2014
Thanks: 2
Given 73 thank(s) in 61 post(s)
(05-02-2015, 03:52 AM)xoft Wrote: Sounds interesting, but I remember reading something about coroutines not being yieldable from within the C API; I might remember wrong, though.
I checked that they are before creating this thread
(05-02-2015, 03:52 AM)xoft Wrote: Also, if you coroutine.yield, you'l need to coroutine.resume at some point, who's gonna do that?
The point of this proposal is that the web thread is sitting in a loop going
release plugin lock,
yield OS thread,
aquire plugin lock,
coroutine resume
(05-02-2015, 03:52 AM)xoft Wrote: I was considering doing asynchronous web requests instead - a webadmin request would create a class object representing it (together with the response) that would be passed to Lua plugins; plugins could service the request immediately or postpone it, storing the object for later (perhaps doing a QueueTask() etc. ) and fulfill the rest of the request later on. This would be perfect for the Gallery previews - they generate asynchronously so right now when the area is previewed for the first time, there is no image to send and a dummy must be sent instead.
This is an alternative. The problem is, how do you deal with the latency of moving over to the tick thread? That's a ~50ms latency penalty. The advantage of my proposal is that in the normal case, we have no penalty compared to a query from anywhere else in the server, its just acquiring a lock. Only in the case of an actual lock conflict does the web thread backoff and let the rest of the server get on with the code.
Posts: 6,485
Threads: 176
Joined: Jan 2012
Thanks: 131
Given 1075 thank(s) in 852 post(s)
Will the rest of the API work normally with this? I have a feeling that it would become rather difficult to actually do anything with such an architecture.
Also, my approach makes it easy to do this for any situation, waiting for virtually anything. Your approach is tied to a single API point and any other API that we want to behave the same will have to be coded the same way.
Also I'm not sure but this sounds like having two sets of APIs, one for regular plugin work and another for web work. That would be most unfortunate.
Posts: 783
Threads: 12
Joined: Jan 2014
Thanks: 2
Given 73 thank(s) in 61 post(s)
Well this did start out as a proposal for avoiding deadlocks generally across the API. I suspect that with your API it will handle web admin waiting for anything but there are other ways to deadlock with the current server. This API can be used to handle any form of deadlock. I will admit that .net async await, which it is inspired by, does tend to permeate the entire code base, but short of moving the API over to continuation passing style like node.js I'm not sure any other solution would solve deadlocks generally.
Posts: 6,485
Threads: 176
Joined: Jan 2012
Thanks: 131
Given 1075 thank(s) in 852 post(s)
I think I see one huge problem with this - object lifetime. If a plugin yields while it has any cEntity / ... objects, those are not guaranteed to be valid after it is resumed. We'd have to first fix this and then it would be possible to yield out of functions.
Also I'm still not sure how this would work with for example hook callbacks - if we yield out of it, when is the callback going to finish?
I admit that the concept of coroutines still eludes me in its full, so I'm a bit worried about everything.
Posts: 783
Threads: 12
Joined: Jan 2014
Thanks: 2
Given 73 thank(s) in 61 post(s)
You're right, lifetime is the big issue. It may be that the only truly safe solution is node.js style continuations, where a callback is not guaranteed to be executed immediately. As for hook callbacks, the original design was that you wouldn't call this from within hooks, however if you where to make it part of the API, then you'd have to modify the hook calling code to do the resumption.
|