MSLNZ / msl-loadlib

Load a shared library (and access a 32-bit library from 64-bit Python)
MIT License
75 stars 17 forks source link

Interprocess communication thread-safe? #20

Closed Bassir closed 5 years ago

Bassir commented 5 years ago

What is the best way to have multiple threads sending requests to the 32 bit process? I have a multi-threaded 64-bit application that needs to interface with a 32-bit API so I used this module to communicate with a 32-bit process running the 32-bit DLL/API but I notice that when multiple threads send requests, I get a crash with the following message:

 File "E:\Anaconda3\lib\site-packages\msl\loadlib\client64.py", line 265, in re
quest32
    self._conn.request('GET', method32)
  File "E:\Anaconda3\lib\http\client.py", line 1229, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "E:\Anaconda3\lib\http\client.py", line 1240, in _send_request
    self.putrequest(method, url, **skips)
  File "E:\Anaconda3\lib\http\client.py", line 1098, in putrequest
    raise CannotSendRequest(self.__state)
http.client.CannotSendRequest: Request-sent

Any advice on how to implement thread-safe inter-process communication? I was thinking perhaps a thread that queues up commands and sends requests, but I'm not sure where to start. I was hoping the inter-process communication implementation here would be able to do this automatically, but apparently does not.

jborbely commented 5 years ago

Unfortunately, inter-process communication is currently not thread safe. Right now, the problem for using multithreading is that a single pickle file is used for inter-process communication and therefore a Client64 is not thread-safe.

Does your 32-bit DLL share internal data between the various API functions that you call or does every API function do something that is independent from all other API functions?

Assuming that your API functions are independent, one suggestion could be:

import threading
from msl.examples.loadlib import Cpp64

results = []

def create_client(a, b):
    c = Cpp64()
    results.append(c.add(a, b))

threads = [threading.Thread(target=create_client, args=(i, i+2)) for i in range(5)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(results)

My suggestion above is to create 5 Clients and run each Client in a separate thread. Would something like that work for you? (this example is runnable, so you can copy and paste it to test it on your end)

Bassir commented 5 years ago

Is it possible to bypass pickling so that I can just have one way communication in a way that is thread-safe?

Would 5 clients be a better option than creating a thread that manages and queues requests before sending them out to Server32.

jborbely commented 5 years ago

In addition to a single pickle file being used, a Client64 uses the HTTPConnection class to communicate with the 32-bit server. The HTTPConnection class is not thread safe. So even if you bypassed pickling (for example, sending a POST command instead of a GET command to the server) the HTTPConnection would not allow using threads.

I'm not sure if creating N clients (I chose 5 at random in my example above, you can use as many threads as you like) is the better option. It would depend on your definition of better. I know that creating N clients in N threads will work, but I'm not sure of your use case. I guess that I would try both of your suggestions and see what code is (1) more readable and (2) has better performance.

jborbely commented 5 years ago

Yes, a task queue could make sense. In order for me to provide additional feedback I would need to see example code that illustrates what you are trying to do and an explanation of what does not work in your code.

Does creating N clients in N threads solve the issue and you are just brainstorming alternative solutions that might be more elegant? or, does nothing work that you've tried so far?

jborbely commented 5 years ago

I'm closing this issue because I assume that you have come up with a solution to your problem since your last comment.