Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Problem with callbacks

Discussion in 'Scripting' started by nopcode, Apr 29, 2011.

  1. nopcode

    nopcode

    Joined:
    Apr 29, 2011
    Posts:
    67
    TL;DR: DllImport delegates lock up on exit from multithreaded dll.

    Been having a major problem with this for the past two days, and up to the point where Unity just isn't going to cut it for us because of it.

    We have a C library that send back periodic data via a typical callback function; this callback is working in a separate thread that reads data from a source and pings it to the listener. The C code dll itself is written with just Windows SDK linked static, so there is no other stuff going on there.

    The callback setup is defined in the C .h header as:

    typedef void (CALLBACK *StatusCallback)(void handle,int status, void * userData);
    void RegisterStatusCallback(void* handle,StatusCallback* cb, void* userData);

    Translating this to C# is:

    internal delegate void StatusCallbackDel(IntPtr handle, int status, IntPtr userData);
    [DllImport(dllname)]
    internal static extern int RegisterStatusCallback(IntPtr handle,StatusCallbackDel cb, IntPtr userData);

    I've put my dllname.dll file in Assets/Plugins

    In my trival C# class, I have:

    public class Class1
    {
    private StatusCallbackDel myCallback;
    private static void StatusCallback(IntPtr handle, int status, IntPtr userData)
    {
    // does nothing
    }

    public Class1()
    {
    myCallback = new StatusCallbackDel(StatusCallback);
    RegisterStatusCallback(IntPtr.Zero, myCallback, IntPtr.Zero);
    }

    The callback works fine, and I get values into my StatusCallback function just lovely; I've put Debug.Log there to test it and it works. Everything is happy *until* I exit the program or "stop playing" inside the Unity editor. Then the Unity game or Unity editor locks up, and I have to kill it with Task Manager.

    I've also tried adding this class as a component for a GUI object so that it can get OnApplicationQuit to call a "stop the thread" function, and that doesn't matter.

    If I modify my C source code so that it never actually calls the callback - but still takes the pointer via RegisterStatusCallback - then I never have a problem, but I also never get anything. Debug.Log in the Class1 destructor doesn't get called if I let the callback go through; otherwise it does work.

    So it looks to me like something is going wrong with handling callbacks from a multithreaded unmanaged C dll? I've tried this exact same code in a console C# application and LabView and it works fine and never locks up like Unity does.

    FWIW, I've also tried compiling the class in VS2008 and just accessing the DLL without using Mono directly, but the problem persists.

    Suggestions?
     
    Last edited: Sep 22, 2012
  2. nopcode

    nopcode

    Joined:
    Apr 29, 2011
    Posts:
    67
    Here is a cookbook way to reproduce this problem, along with very, very, very simple code to use for testing.

    Four zip files are attached: ExternalThreader.zip, ExternalThreaderDLL_nocallback.zip, ExternalThreaderConsumer.zip, and TestExternalCallback.zip.

    ExternalThreader.zip contains the source code and project (Vs2008 project, compiled as static so no MSVC* dlls) for the external “C” dll that does the following:
    When StartExternalThreader is called, starts a thread that calls the registered callback once every 1000 ms, with an incrementing “status” count that starts at 100. This continues to run until StopExternalThreader is called, which then clears the callback value, sets an event flag to break out of the threaded loop, and sleeps for 120 ms to ensure the thread has gone.

    The ExternalThreaderConsumer.zip is a C# wrapper class (VS2008 project, set up as .NET 2.0) around the ExternalThreader.dll, in that the constructor registers the callback and starts the thread, and the destructor does the opposite. The callback delegate handler does nothing and is not exposed as a public item.

    The TestExternalCallback.zip is a Unity project that has nothing but a main camera and an empty game object that has a single C# script attached to it. This script references the ExternalThreaderConsumer.ThreaderConsumer class, and constructs it on Start() and does nothing with it. The script class sets the internal pointer to ThreaderConsumer when it is destroyed (this is probably not needed).

    If either compiled and run, or played in the Unity editor, the program and script will all work just fine. The problem occurs when either the compiled .exe is exited, or if played back in Unity, the Unity editor is exited. Then either one will lock up because of the callback.

    If the ExternalThreader.cpp source code is modified on line 38 to comment out:
    callbackFP(statusCounter);
    And recompiled and put in the project root (or you can unzip ExternalThreaderDLL_nocallback.zip which has this done for you already), replacing the existing ExternalThreader.dll file, then the problem does not occur.

    Note that there is NO callbacks being done within the actual Unity engine; the callbacks themselves are being handled inside of a "3rd party" C# class library that you just happen to have the source to.

    If you switch from Unity Free to Unity Pro, and you put the source code the ExternalThreaderConsumer class into your project, and do not use the ExternalThreaderConsumer.dll, the exact same problem still occurs.

    Note that at NO TIME are any calls to Unity being done from within a thread; the callback function is completely empty. However, you can verify the callback handler is being processed with Unity Pro by adding in a Debug.Log call in the callback handler.

    Flat-out, I need to know how to resolve this before we can move forward with our project.
     

    Attached Files:

  3. adam718

    adam718

    Joined:
    Mar 11, 2012
    Posts:
    58

    Hi!
    I have same problem.
    * I register callback to dll.
    * Dll calls callback in other thread.
    * When I close unity standalone application, It doesn't close.

    I have been working for over 3 weeks with that serious problem but I didn't find any solution.
    If you solve that problem, please help me.

    Thank you,
    Best Regards,
    Adam.
     
    Last edited: May 11, 2012
  4. tkirchner

    tkirchner

    Joined:
    Oct 7, 2009
    Posts:
    1
    Has anyone found a solution to this? I am having the same issue.
     
  5. Jerrot

    Jerrot

    Joined:
    Aug 3, 2012
    Posts:
    5
    Same problem here. Any official words or workarounds? Serious blocker to me.
     
  6. r618

    r618

    Joined:
    Jan 19, 2009
    Posts:
    1,298
    hi, have a look @ http://forum.unity3d.com/threads/90128-Unity-Threading-Helper

    I'd just try to wrap C calls/callbacks by UnityThreadHelper and see if it helps....

    I got callbacks from different thread (FMOD) just fine and no hangups/crashes on app quitting, but note that in same assembly though, so I'm not completely sure this helps in any way in your case
     
  7. adam718

    adam718

    Joined:
    Mar 11, 2012
    Posts:
    58
    Hi!,
    I posted the problem to unity team, and they said they would fix it in later version.
    Before that, I am going to try to use it.
    Thank you r618,
     
  8. Jerrot

    Jerrot

    Joined:
    Aug 3, 2012
    Posts:
    5
    Thanks, I've seen that one before, however I'm a little lost how this might help me, my threads are started (and probably can only be started) by the (unmanaged) C++ DLLs themselves. The UnityThreadHelper is a very nice help to work with threads on the managed side for sure though.

    Also thank you Adam for the update.
     
  9. adam718

    adam718

    Joined:
    Mar 11, 2012
    Posts:
    58
    But if you declare callback callee in ThreadHelper Thread(not main thread) in C# as in c++, then crash will be fixed, I think.
    I am sorry I didn't try it yet cause I have no time right now.
     
  10. r618

    r618

    Joined:
    Jan 19, 2009
    Posts:
    1,298
    that's how I would go about it
    if the crash is due to conflict between main engine and external thread it might help, but without testing it's more or less guesswork
     
  11. Jerrot

    Jerrot

    Joined:
    Aug 3, 2012
    Posts:
    5
    Hmm, I tried including UnityThreadHelper and declared my callee in an ActionThread now, which simply loops forever after declaration, waiting for "ShouldStop". I call "Abort()" on the thread on application quit. As before, everything works fine, but hangs on exit of the Windows Build. :-(

    Please let me know if you managed to get it working this way, maybe I missed something.

    EDIT: On second thoughts I don't understand what this should "fix" anyway. The callee gets declared in another thread now, but does that alone really make the callback function running in that thread?
     
    Last edited: Aug 6, 2012
  12. Jerrot

    Jerrot

    Joined:
    Aug 3, 2012
    Posts:
    5
    On a side-note - my project differs a little here, I'm calling several functions of the DLL which again send me responses via callbacks, so I guess if the ThreadHelper class might be of any use for me, I'd have to do these calls from the managed thread as well and hope, that the problem won't appear if the native thread is a subthread of my managed ActionThread. I'll try this in a while and send you some update.
     
    Last edited: Aug 6, 2012
  13. Jerrot

    Jerrot

    Joined:
    Aug 3, 2012
    Posts:
    5
    And last not least - that didn't work out either for me. I guess it remains a problem with mono's clean-up. :(
     
  14. bagel

    bagel

    Joined:
    Aug 2, 2011
    Posts:
    1
    I had this problem too, and after fighting with various things in the dll (nulling out the callback ref, etc) and in the c# script eventually I ended up adding this bit of insanity. Please feel free to give feedback if this is a bad idea, I'm relatively new to Unity and stumbled upon this workaround... I threw this in a MonoBehaviour C# script.

    Code (csharp):
    1. void OnApplicationQuit()
    2.     {      
    3.         #if UNITY_EDITOR
    4.             Debug.Log("EDITOR NO CLOSE...");
    5.         #elif UNITY_STANDALONE_WIN //Seemingly fires in editor as well...
    6.             System.Diagnostics.Process.GetCurrentProcess().Kill();
    7.         #endif         
    8.     }
    I mean for all I know this is short-circuiting something that is supposed to be happening in a garbage collection phase causing a freeze or something similar but it does seemingly avoid the lock up on exit...

    One caveat is that it DOES still lock up when you eventually close the editor but in a standalone it at least closes without having to open the Windows System Manager...
     
  15. Tomas1856

    Tomas1856

    Unity Technologies

    Joined:
    Sep 21, 2012
    Posts:
    3,858
    When the application exits - UnityEditor or WindowsStandalone player, mono scans all the threads in the application and tries to close them, it also catches threads created by you, even if they were created from C++ not from C#, because mono didn't expect this, at some point it goes into infinite loop.

    Mono uses OpenThread (THREAD_ALL_ACCESS, TRUE, threadID) to access the designated thread. So to workaround this problem, you have to create a thread using DELETE restriction by passing special SECURITY_ATTRIBUTES, this way mono will ignore this thread.

    Hope that helps
     
  16. RandomOutput

    RandomOutput

    Joined:
    Mar 11, 2015
    Posts:
    2
    I realize this was an old thread, but I wanted to share the solution to this problem at worked for me for any future Google searchers such as myself.

    The salient bit is to make sure your C++ Plugin cleans up all its threads when (or before) Unity's OnApplicationQuit() Monobehavior method is called. Here's a rather lengthy code example showing how to handle this in the C++, C# binding, and C# Monobehavior respectively.

    For the tldr; on the code, look at the `void OnApplicationQuit()` function in the monobehavior and the `void CleanupCallback()` function in the C++.

    Code (CSharp):
    1. // C++ Unmanaged Code
    2.  
    3. // Create macros for cleaning up DLL decoration for the plugin
    4. #define MYPLUGIN_C extern "C"
    5.  
    6. #if defined (_WIN32)
    7.   #define MYPLUGIN_EXPORT __declspec(dllexport)
    8. #else
    9.   #define MYPLUGIN_EXPORT
    10. #endif
    11.  
    12. #include <chrono>
    13. #include <thread>
    14.  
    15. // Define our callback "delegate"
    16. typedef int(*callback_t)(int a, int b);
    17.  
    18. // Store an instance of our callback
    19. static callback_t my_callback;
    20.  
    21. // Our thread that will call the callback
    22. std::thread callbackWorker;
    23.  
    24. // Get the callback
    25. MYPLUGIN_C MYPLUGIN_EXPORT void RegisterCallback(callback_t cb) {
    26.   my_callback = cb;
    27. }
    28.  
    29. // Spawn a thread to use the callback off the
    30. // Unity main thread.
    31. MYPLUGIN_C MYPLUGIN_EXPORT void UseCallback() {
    32.   callbackWorker = std::thread(loopCallback);
    33.   return;
    34. }
    35.  
    36. // The call to callbackWorker.join() here is the critical bit.
    37. // We need to do this to every unmanaged thread we've spawned, and
    38. // we have to block until that is done.
    39. MYPLUGIN_C MYPLUGIN_EXPORT void CleanupCallback() {
    40.   my_callback = NULL;
    41.   callbackWorker.join();
    42. }
    43.  
    44. // Just some functions to use the callback in an easily testible way.
    45. void FireCallback() {
    46.   if (my_callback == NULL) {
    47.     return;
    48.   }
    49.   my_callback(0, 1);
    50.   return;
    51. }
    52.  
    53. void loopCallback() {
    54.   for (int i = 0; i < 10; i++) {
    55.     FireCallback();
    56.     std::this_thread::sleep_for(std::chrono::milliseconds(20));
    57.   }
    58. }
    59.  
    60. // C# Binding
    61. class PluginBinding {
    62.   // Define the callback
    63.   public delegate int MyCallback(int a, int b);
    64.  
    65.   // Foor using the callback and marshalling data into the Unity main thread
    66.   public static int num = 0;
    67.   public static object lk = new object();
    68.  
    69.   // Create bindings for each of our DLL functions
    70.   [DllImport("MyPlugin")]
    71.   public static extern void RegisterCallback(MyCallback cb);
    72.  
    73.   [DllImport("MyPlugin")]
    74.   public static extern void CleanupCallback();
    75.  
    76.   [DllImport("MyPlugin")]
    77.   public static extern void UseCallback();
    78. }
    79.  
    80.  
    81. // C# Monobehavior (in Unity scene)
    82. using UnityEngine;
    83. using System.Collections;
    84. using System.Runtime.InteropServices;
    85.  
    86. public class PluginCallbackTest : MonoBehaviour {
    87.   // Define our callback function
    88.   public static int functionForCallback(int a, int b) {
    89.     lock (lk) {
    90.       num ++;
    91.     }
    92.     return a + b;
    93.   }
    94.  
    95.   // Use this for initialization
    96.   void Start () {
    97.     PluginBinding.RegisterCallback(functionForCallback);
    98.     PluginBinding.UseCallback();
    99.   }
    100.  
    101.   // Update is called once per frame
    102.   void Update () {
    103.     lock (PluginBinding.lk) {
    104.       Debug.Log("num: " + PluginBinding.num);
    105.     }
    106.   }
    107.  
    108.   // Make sure we tell out DLL to cleanup its threads before exit.
    109.   void OnApplicationQuit() {
    110.     PluginBinding.CleanupCallback();
    111.   }
    112. }
    113.  
    114.  
     
  17. jjanzer_tafi

    jjanzer_tafi

    Joined:
    Mar 4, 2017
    Posts:
    477
    Hi Thomas, sorry for necroing this old post...

    Your suggestion worked great on windows where I have access to the SetSecurityInfo calls and ACL bits necessary for this. Do you have any suggestions for what to do on other platforms (eg: OSX). Would my native plugin need to start accessing Mono to solve this issue?

    I do agree with RandomOutput with properly closing the threads to really solve this issue however.


    Also for anyone that wants to know how to fix this w/o properly cleaning up your threads... This is the code I wrote for Windows that works. I use boost threads, so I got the native handle first.
    Code (CSharp):
    1. bool Thread::changeSecurityForMono() {
    2.     auto handle = m_thread.native_handle();
    3.  
    4.     PSID pSidOwner = NULL;
    5.     PSID pSidGroup = NULL;
    6.     PACL pDacl = NULL;
    7.     PACL pSacl = NULL; //we don't care about this one
    8.     PSECURITY_DESCRIPTOR pSD = NULL;
    9.  
    10.     //let's get the existing security information, we'll check the DACL for the DELETE bit
    11.     auto result = GetSecurityInfo(
    12.         handle,
    13.         SE_KERNEL_OBJECT,
    14.         DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION,
    15.         &pSidOwner,
    16.         &pSidGroup,
    17.         &pDacl,
    18.         NULL,
    19.         &pSD
    20.     );
    21.  
    22.     if(result != ERROR_SUCCESS){
    23.         std::cerr << "Failed to GetSecurityInfo for windows thread" << std::endl;
    24.         return false;
    25.     }
    26.  
    27.     //our acl pointer for our new security info if we need it
    28.     PACL pDaclNew = NULL;
    29.     EXPLICIT_ACCESS ea[1];
    30.     SECURITY_DESCRIPTOR sdNew;
    31.  
    32.     ZeroMemory(&ea, 1 * sizeof(EXPLICIT_ACCESS));
    33.     ea[0].grfAccessPermissions = DELETE;
    34.     ea[0].grfAccessMode = SET_ACCESS;
    35.     ea[0].grfInheritance= NO_INHERITANCE;
    36.     ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
    37.     ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
    38.     ea[0].Trustee.ptstrName  = (LPTSTR) pSidOwner;
    39.  
    40.     result = SetEntriesInAcl(
    41.         1,
    42.         &ea[0],
    43.         pDacl,
    44.         &pDaclNew
    45.     );
    46.     if(result != ERROR_SUCCESS){
    47.         std::cerr << "SetEntriesInACL failed: " << GetLastError() << std::endl;
    48.         return false;
    49.     }
    50.  
    51.     result = InitializeSecurityDescriptor(&sdNew, SECURITY_DESCRIPTOR_REVISION);
    52.  
    53.     if(!result){
    54.         std::cerr <<"InitializeSecurityDescriptor failed: " << GetLastError() << std::endl;
    55.         return false;
    56.     }
    57.     result = SetSecurityDescriptorDacl(&sdNew,TRUE,pDaclNew,FALSE);
    58.     if(!result){
    59.         std::cerr << "SetSecurityDescriptorDacl failed" << std::endl;
    60.         return false;
    61.     }
    62.  
    63.     result = SetSecurityInfo(
    64.         handle,
    65.         SE_KERNEL_OBJECT,
    66.         DACL_SECURITY_INFORMATION,
    67.         pSidOwner,
    68.         pSidGroup,
    69.         pDaclNew,
    70.         pSacl
    71.     );
    72.  
    73.     if(result != ERROR_SUCCESS)
    74.     {
    75.         std::cerr << "Failed to update thread permissions: " << GetLastError() << std::endl;
    76.         return false;
    77.     }
    78.  
    79.     //we were successful in setting the DELETE bit
    80.     return true;
    81. }
     
  18. johnfrog

    johnfrog

    Joined:
    May 7, 2015
    Posts:
    13
    I'm writing a plugin which can call c# code on Unity's render thread - I'm doing this as I've wrapped the Metal library in c# and I want to create my own kernels and encoders in the same c# project.

    In a nutshell - I create command buffers and call IssuePluginEventAndData to send events to render thread in the native plugin. Then I can send control back via invoking callback to C# and I can dispatch my kernels etc.

    Code (CSharp):
    1.  
    2. private IntPtr _renderEventAndDataFunction;
    3.      
    4.         private void Awake()
    5.         {
    6.             UnityMetal_SetCommandBufferCallback(CommandBufferCallback);
    7.             _renderEventAndDataFunction = UnityMetal.GetRenderEventAndDataFunction();
    8.         }
    9.      
    10.         public void AddKernel(KernelBase kernel, CameraEvent cameraEvent)
    11.         {
    12.             if (_kernels.Any(x => x.Kernel == kernel && x.Event == cameraEvent))
    13.             {
    14.                 return;
    15.             }
    16.  
    17.             var commandBuffer = new CommandBuffer();
    18.  
    19.             var kernelData = new KernelCameraEvent(kernel, cameraEvent, commandBuffer, Camera);
    20.          
    21.             commandBuffer.IssuePluginEventAndData(_renderEventAndDataFunction, 0, kernelData.Pointer);
    22.             Camera.AddCommandBuffer(cameraEvent, commandBuffer);
    23.          
    24.             lock (_renderThreadLock)
    25.             {
    26.                 _kernels.Add(kernelData);  
    27.             }
    28.         }
    29.  
    30.         [DllImport(Constants.PluginName)]
    31.         static extern void UnityMetal_SetCommandBufferCallback(Action<IntPtr> callback);
    32.  
    33.         [MonoPInvokeCallback(typeof(Action<IntPtr>))]
    34.         static void CommandBufferCallback(IntPtr id)
    35.         {
    36.             if (_instance != null)
    37.             {
    38.                 _instance._CommandBufferCallback(id);
    39.             }
    40.         }
    41.  
    42.         private void _CommandBufferCallback(IntPtr id)
    43.         {
    44.             lock (_renderThreadLock)
    45.             {
    46.                 for (var i = 0; i < _kernels.Count; i++)
    47.                 {
    48.                     if (id == _kernels[i].Pointer)
    49.                     {
    50.                         _kernels[i].Kernel.OnDispatch();
    51.                     }
    52.                 }
    53.             }
    54.         }
    And in C

    Code (Boo):
    1. extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
    2. UnityMetal_OnRenderEventAndData(int eventId, void* data){
    3.    
    4.     switch (eventId){
    5.         case 0:
    6.         if (g_UnityMetalContext.EventCallBack && data != NULL){
    7.             g_UnityMetalContext.EventCallBack((long)data);
    8.         }
    9.         break;
    10.     }
    11. }
    This method works on both OSX and iOS - but every time I try to quit Unity it just hangs, and the last thing the logs says is Cleanup mono. If the C method UnityMetal_OnRenderEventAndData is empty then Unity does not hang.
    Unity still hangs if C# method CommandBufferCallback is empty as well.

    Any idea what could be going on with the threads?
     
  19. drcrck

    drcrck

    Joined:
    May 23, 2017
    Posts:
    328
    8 years later — it's still not fixed
     
  20. Rib

    Rib

    Joined:
    Nov 7, 2013
    Posts:
    39
    After hitting this issue I found that in addition to having an OnApplicationQuit handler for cleaning up threads it was also necessary to register an event handler for EditorApplication.quitting to consider the cases where the editor may be closed while still in play mode. It seems that if you exit the Unity editor in this case it won't call your OnApplicationQuit handler and so I would get a hang while there were native threads that would never exit.