Skip to content

Embedded Python interpreter connects over TCP and is permanently disconnected by system sleep #325

Description

@jhery-rdo

Description

GlobalActor::connect_api passes the embedded Python interpreter the API's TCP port, so the interpreter connects to its own process through the CAF middleman:

// src/global/src/global_actor.cpp
mail(connect_atom_v, port_).request(embedded_python, infinite)
# src/embedded_python/src/embedded_python.cpp -> EmbeddedPython::connect(int)
XSTUDIO = Connection(debug=False)
XSTUDIO.connect_remote("127.0.0.1", <port>)

That is a real loopback TCP connection from the process to itself, and the OS is free to tear it down. macOS does so on system sleep. Nothing reconnects it, so after a wake every Python plugin is permanently cut off from the application hosting it, while the rest of the UI keeps working because it uses in-process actors.

Symptoms

  • Python panels stop responding. Clicking a control runs the QML handler, but attribute_changed never reaches Python.
  • ModuleAttribute.set_value() and connection.send() appear to succeed — py_send hands the message to a dead proxy and returns.
  • Every request_receive blocks for the full default_timeout_ms and then raises TimeoutError("Dequeue timeout"). Since py_request uses caf::infinite, CAF never produces an error of its own, so it reads as a hang rather than a failure.
  • The native UI, playback and viewport are unaffected, which makes this look like a plugin bug rather than a transport one.
  • Nothing is logged. Connection.connected is set once in negotiate() and is never cleared by a link failure, and no caf.middleman.* heartbeat is configured, so nothing observes the disconnection.

Reproduction (macOS)

  1. Launch xSTUDIO with Python plugins enabled.

  2. Inspect the sockets — note the loopback pair, both ends owned by the same PID:

    $ lsof -a -nP -p $(pgrep -f xstudio.bin) -iTCP
    xstudio.b 27724 fd  8u  TCP 127.0.0.1:14441 (LISTEN)
    xstudio.b 27724 fd 20u  TCP 127.0.0.1:59506->127.0.0.1:14441 (ESTABLISHED)
    xstudio.b 27724 fd 21u  TCP 127.0.0.1:14441->127.0.0.1:59506 (ESTABLISHED)
  3. Sleep the machine for ~5 minutes, then wake it.

  4. Run the same command against the same, unrestarted process:

    $ lsof -a -nP -p 27724 -iTCP
    xstudio.b 27724 fd  8u  TCP 127.0.0.1:14441 (LISTEN)

    Both file descriptors of the pair are closed outright — not half-open, not TIME_WAIT. The listener survives.

  5. Any Python plugin interaction now fails as described above, for the remaining lifetime of the session. Only a restart recovers it.

Suggested fix

EmbeddedPythonActor already has a connect_atom/caf::actor handler that calls Connection.connect_local(), and py_context::connect_local() already exists — but nothing ever sends it an actor, so that path is currently dead code. Passing apia_ instead of port_ selects it and removes the socket entirely. APIActor answers the whole negotiate() handshake (version, application mode, authenticate), and its authenticate_atom handler admits any sender on the same node, so a local connect succeeds.

The API port is still published, so external clients (xstudiopy, xstudio_control) are unaffected — only the embedded interpreter stops using it.

I have this running locally and will open a PR shortly.

Environment

  • xSTUDIO 1.3.0
  • macOS on Apple silicon (Darwin 25.5.0)
  • Not inherently macOS-specific: any OS that can drop a loopback TCP connection will produce the same result. macOS system sleep is simply a reliable trigger.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions