11
$\begingroup$

I have a large codebase in which some functions make calls to Python TCP servers. The code used to work perfectly in Mathematica 12.2, but has not worked since I updated to Mathematica 12.3 last night.

I have managed to isolate the problem to the call to WriteString. The following is a minimum illustration of the issue. Here is the Python server:

import socketserver
import json
import time


class TCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        bufSz = 2048
        while True:
            self.data = self.request.recv(bufSz)
            if self.data:
                jsonReq = json.loads(self.data.strip())
                print(f"Request: {jsonReq}")
                jsonRes = json.dumps({"res": len(jsonReq["arg"])})
                print(f"Response: {jsonRes}")
                self.request.sendall(bytes(jsonRes, "utf-8"))
            else:
                time.sleep(0.10)
                self.data = ""
                continue


def main():
    host, port = "localhost", 9990
    with socketserver.TCPServer((host, port), TCPHandler) as server:
        print(f"Test server now running at {host} on port {port}")
        server.serve_forever()


if __name__ == "__main__":
    main()

And here is the Wolfram Language code that calls it:

ClearAll[sock];
sock = SocketConnect[{"localhost", 9990}, "TCP"];

ClearAll[params];
params = ExportString[<|"arg" -> "test string"|>, "JSON"];

Module[{res},
    WriteString[sock, params];
    res = ByteArrayToString@SocketReadMessage[sock];
    ImportString[res, "JSON"]
]

This exact set-up used to work for me reliably until Mathematica 12.2. But now the execution hangs at the WriteString command.

I have tried turning off Windows firewall entirely. The problem persists.

Any assistance would be gratefully acknowledged.

$\endgroup$
16
  • $\begingroup$ Strange. It doesn't seem as though any of the functions involved have been updated in 12.3, so I'm not sure why this wouldn't be working, but it doesn't seem like it is. $\endgroup$
    – Carl Lange
    Commented May 21, 2021 at 18:16
  • $\begingroup$ I got a good connection in the end in 12.3, but still hangs. $\endgroup$
    – flinty
    Commented May 21, 2021 at 18:17
  • 1
    $\begingroup$ Thanks for the confirmation, @CarlLange and @flinty. I hope someone at Wolfram Research will at least provide a workaround soon. This represents a huge problem for me. $\endgroup$ Commented May 21, 2021 at 18:35
  • 1
    $\begingroup$ It looks like there has been a major rewrite of the ZeroMQLink in 12.3.0. Since this is a Paclet WRI might be able to fix it by a PacletUpdate. $\endgroup$
    – Philipp
    Commented May 23, 2021 at 15:41
  • 4
    $\begingroup$ As a workaround you can copy the ZeroMQLink Paclet from 12.2.0 (/Applications/Mathematica.app/Contents/SystemFiles/Links/ZeroMQLink) to $UserBaseDirectory/Paclets/Repository (eg. /Users/Philipp/Library/Mathematica/Paclets/Repository) and increase the version number in PacletInfo.m to 1.3.0. This worked for a test on my machine. Note: after a fix has been provided this paclet has to be deleted. $\endgroup$
    – Philipp
    Commented May 23, 2021 at 15:56

1 Answer 1

3
$\begingroup$

I think there is a problem with your python TCP server implementation.

If you check the corresponding documentation, especially the example section, you may witness that the provided TCP client code does not always work either with your server.

client.py:

import socket
import sys

HOST, PORT = "localhost", 9990
data = " ".join(sys.argv[1:])

# Create a socket (SOCK_STREAM means a TCP socket)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    # Connect to server and send data
    sock.connect((HOST, PORT))
    sock.sendall(bytes(data + "\n", "utf-8"))

    # Receive data from the server and shut down
    received = str(sock.recv(1024), "utf-8")

print("Sent:     {}".format(data))
print("Received: {}".format(received))

run it with $ python3 client.py hello!. It sometimes hang on my machine (MacOS, Python 3.9.4).

Now if you replace your python server with the example implementation, it's possible to interact from the Wolfram Language.

This leads me to conclude that there must be a problem with your code sample. Could you double check this first, then we can investigate further what your project issue is. Let me provide few comments about the Python server to help debugging. First of all, why do you wrap the handler logic inside a while? I don't think it's necessary since the self.request.recv is a non busy wait. It may prevent correct connection handling. Also, why would you sleep if no data was received? Maybe that makes sense in your project, in which case disregard this comment, but in the example it seems unnecessary and confusing.

When I drop the while loop, and wrap the code inside a try / except, I'm able to communicate with a Wolfram Kernel. Here is a sample python server in case it helps:

import socketserver
import json
import time


class TCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        bufSz = 2048
        try:
            print("Waiting for a request...")
            self.data = self.request.recv(bufSz)
            print("Request received!")
            if self.data:
                jsonReq = json.loads(self.data.strip())
                print(f"Request: {jsonReq}")
                jsonRes = json.dumps({"res": len(jsonReq["arg"])})
                print(f"Response: {jsonRes}")
                self.request.sendall(bytes(jsonRes, "utf-8"))
            else:
                print("Empty request, sleeping.")
                time.sleep(0.10)
                self.data = ""
        except Exception as e:
            print("Error: ", e)

def main():
    host, port = "localhost", 9990
    with socketserver.TCPServer((host, port), TCPHandler) as server:
        print(f"Test server now running at {host} on port {port}")
        server.serve_forever()


if __name__ == "__main__":
    main()

Hopefully that will help you fixing your issue.

$\endgroup$
6
  • $\begingroup$ If you drop the loop, then the connection isn't kept alive and the socket isn't re-used which is why OP is using this code. I came up with the code originally in this answer . There could well be a problem with it, I'm not an expert in networking, but it worked sufficiently well for OP since then. $\endgroup$
    – flinty
    Commented May 25, 2021 at 16:15
  • $\begingroup$ Thanks a lot for your suggestions. I will try them when I get back this evening. But @flinty has already answered one of your questions. Without the while loop, the connection isn't kept alive, which means that all of the socket-related initialisation (such as loading of trained models, etc.) has to be done each time. That approach is not feasible. This code has worked without error until now. $\endgroup$ Commented May 25, 2021 at 17:05
  • $\begingroup$ I'm not sure to understand what you mean @flinty. It's possible to use the Python server code I've provided and run this function from the other answer multiple time: getResFromPython[sock_][arg_String]:=(WriteString[sock,ExportString[<|"arg"->arg|>,"JSON"]]; ByteArrayToString@SocketReadMessage[sock]); Map the function: vals = Map[getResFromPython[sock], {"arg1", "arg10", "arg100"}]. It'll return {"{\"res\": 4}", "{\"res\": 5}", "{\"res\": 6}"}. (tested with ZMQ Paclet version 1.2.1) $\endgroup$
    – Dorian B.
    Commented May 26, 2021 at 9:52
  • $\begingroup$ @DorianB. that looks like it's fixed then, because prior to this in the other answer the connection would close after an unspecified timeout and you couldn't do a WriteString twice without a lockup, i.e there was no way to keep alive the connection because python would send a FIN, mathematica would ignore it, and then python would send a RST killing it and preventing further WriteStrings. $\endgroup$
    – flinty
    Commented May 26, 2021 at 10:48
  • $\begingroup$ @DorianB. sorry, I was really preoccupied with a presentation I had to make yesterday, so I could not revisit this problem until this morning. But I can now confirm that removing the while loop from the TCP handler has corrected the problem, and my code is running once again. I have accepted your answer. $\endgroup$ Commented May 30, 2021 at 17:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.