[Home] [Downloads] [Search] [Help/forum]


Register forum user name Search FAQ

Gammon Forum

[Folder]  Entire forum
-> [Folder]  MUSHclient
. -> [Folder]  General
. . -> [Subject]  Asynchronous Calling Mechanism

Asynchronous Calling Mechanism

It is now over 60 days since the last post. This thread is closed.     [Refresh] Refresh page


Posted by Pink Olifaunt   (11 posts)  [Biography] bio
Date Sat 01 Dec 2007 11:52 AM (UTC)

Amended on Sat 01 Dec 2007 11:58 AM (UTC) by Pink Olifaunt

Message
Hello All,

I've recently been dabbling with a system that runs in its own thread in a DLL loaded by a MUSHclient plugin. Naturally the thing needs a way to call MUSHclient scripting functions, but at the moment there's no easy way to accomplish that. I think the best current solution is to have the plugin listen for UDP datagrams on the UI thread, but that's pretty indirect, and for my application the potential for millisecond-level latencies as a result of sending data through the network stack is undesirable.

I have what I think is a pretty reasonable mechanism for making calls to script functions "directly" from another thread working as a patch to 4.18 (see the files below). It works by sending messages to the MUSHclient UI thread, which then executes the calls and a can return a result. I suppose I might as well explain the thinking behind it in case anyone's interested.

Safely calling functions from another thread is hard for several reasons. Say we pass our plugin's lua_State* out to a worker thread: what if we then decide to close the world and the plugin is destroyed, taking the lua_State along with it? The thread has to be notified before this happens so it knows it should consider its pointer to the interpreter invalid. For the sake of argument, say we can accomplish this notification, so that the worker can assume its lua_State pointer (or the equivalent) is valid. Is it OK to call a MUSHclient function from a foreign thread? Well, no. The innards of MUSHclient aren't designed to be thread-safe, and it would corrupt state and generally cause havoc if certain functions were to be run at the same time in different threads.

So, my first thought was to have some function that allowed a worker thread to "Lock" the Lua state, which would mean it acquired a mutex that synchronized access to essentially all of MUSHclient. The UI thread would block waiting for the mutex, allowing the worker thread to safely call functions and manipulate state. This approach doesn't work for practical reasons. The MFC makes a distinction between "UI threads" and "worker threads" which it enforces by putting some data in a TLS slot, which is checked at the beginning of every MFC function. Calling MFC functions from the wrong thread just gets you a failed assertion. In a similar vein the creator of the foreign thread would have to worry about having the right per-thread CRT data structure in TLS in order to be compatible with the CRT functions that MUSHclient calls.

The patch below avoids those problems by executing calls on the UI thread. The patched MUSHclient exports a function called "CallPluginAsync" which takes a world ID, plugin ID, function name and a string parameter. Calls can be either blocking or non-blocking, the former meaning that CallPluginAsync will not return until the script function has finished executing. At the moment CallPluginAsync (and its friend to be introduced shortly) are just C functions exported by MUSHclient.exe. A caller must get pointers to them using code like this:

HANDLE hMushClient = GetModuleHandle("MUSHclient.exe");
CallPluginAsync = (CallPluginAsyncPtr)GetProcAddress(hMushClient, "CallPluginAsync");

I looked at adding equivalents to the scripting APIs that could be called provided the calling thread knew its pointer to the scripting engine was still valid, as described above. As it turns out, this isn't practical. IActiveScript simply refuses to work from a thread other than the one that set its 'site', in a similar way to MFC. Lua has provision for thread safety in that it allows an application to define functions that it will call to sychronize access to its internals, but providing those hooks would make the interpreter take locks during common operations and probably slow it down. It seems overkill just to export one rarely-used function.

As always, correct multithreading involves some suffering on the part of the programmer. Even though CallPluginAsync guarantees to work (or at least return an error) regardless of the state MUSHclient is in when it's called, it's necessary for a plugin controlling a thread to stop the thread in OnPluginClose because any DLL loaded using Lua's loadlib function will be forcibly unloaded when the Lua interpreter shuts down. The usual way to stop a thread using the Win32 API is to call WaitForSingleObject on the thread handle. Unfortunately this can cause a deadlock if the worker thread is trying to make asynchronous calls, because the UI thread is blocked waiting for the worker to terminate, while the worker is blocked waiting for a call to dispatch, which will never happen because the UI thread... you get the picture.

The function DispatchAsyncCalls can be called on the UI thread to avoid this situation. The code to safely wait for thread termination becomes something like:


stopFlag = 1;
do {
    DispatchAsyncCalls(); 
    rc = WaitForSingleObject(hThread, 10);
} while (rc == WAIT_TIMEOUT);
CloseHandle(hThread);


Anyway, the page below has the patch itself, and a zip with a modified MUSHclient binary and a demo plugin DLL that you can play with, complete with C source code (and a suprcool 3D star!1). If there's any interest in integrating this into the mainline then I'll be happy to write a help file (hopefully a bit more coherent than this post), but the published patches don't work correctly for me so I have had some trouble getting up to the latest version of the help contents.

All files are at:

http://pink.olifaunt.googlepages.com/




http://pink.olifaunt.googlepages.com/home
[Go to top] top

Posted by Nick Gammon   Australia  (23,006 posts)  [Biography] bio   Forum Administrator
Date Reply #1 on Sat 01 Dec 2007 10:18 PM (UTC)
Message
Sounds interesting, thanks for sharing it.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Pink Olifaunt   (11 posts)  [Biography] bio
Date Reply #2 on Sun 17 Feb 2008 08:56 PM (UTC)
Message
As an addendum to this, I noticed that Win32 provides MsgWaitForMultipleObjects which you could use instead of calling DispatchAsyncCalls. It's only available on Win2K and higher though.


http://pink.olifaunt.googlepages.com/home
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


11,631 views.

It is now over 60 days since the last post. This thread is closed.     [Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.

[Home]


Written by Nick Gammon - 5K   profile for Nick Gammon on Stack Exchange, a network of free, community-driven Q&A sites   Marriage equality

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( https://gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Hosted at HostDash]