Skip to content

Commit

Permalink
Check if another process is already listening on the port before tryi…
Browse files Browse the repository at this point in the history
…ng to spawn (#3501)

### What
- Resolves: #2551

Before we spawn, we now try connecting to the port to see if it's in use
or not, allowing us to print a better error message and not waste time
kicking off a subprocess that's just going to fail.

Before:
```
$ python docs/code-examples/point3d_random.py
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/jleibs/rerun/rerun_py/rerun_sdk/rerun/__main__.py", line 16, in <module>
    main()
  File "/home/jleibs/rerun/rerun_py/rerun_sdk/rerun/__main__.py", line 12, in main
    exit(bindings.main(sys.argv))
RuntimeError: Failed to bind TCP address "0.0.0.0:9876". Another Rerun instance is probably running. Address already in use (os error 98)
```

After:
```
$ python docs/code-examples/point3d_random.py
DEV ENVIRONMENT DETECTED! Re-importing rerun from: /home/jleibs/rerun/rerun_py/rerun_sdk
Found existing process on port 9876. Trying to connect.
```

### Checklist
* [x] I have read and agree to [Contributor
Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and
the [Code of
Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md)
* [x] I've included a screenshot or gif (if applicable)
* [x] I have tested [demo.rerun.io](https://demo.rerun.io/pr/3501) (if
applicable)

- [PR Build Summary](https://build.rerun.io/pr/3501)
- [Docs
preview](https://rerun.io/preview/4c212d77d5ff82020b1df508dbdf5606c7bd8bad/docs)
<!--DOCS-PREVIEW-->
- [Examples
preview](https://rerun.io/preview/4c212d77d5ff82020b1df508dbdf5606c7bd8bad/examples)
<!--EXAMPLES-PREVIEW-->
- [Recent benchmark results](https://ref.rerun.io/dev/bench/)
- [Wasm size tracking](https://ref.rerun.io/dev/sizes/)
  • Loading branch information
jleibs authored Sep 28, 2023
1 parent 88ee576 commit 8c910aa
Showing 1 changed file with 49 additions and 13 deletions.
62 changes: 49 additions & 13 deletions rerun_py/rerun_sdk/rerun/sinks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import logging
import socket

import rerun_bindings as bindings # type: ignore[attr-defined]

Expand Down Expand Up @@ -142,8 +143,29 @@ def serve(
bindings.serve(open_browser, web_port, ws_port, recording=recording)


# TODO(jleibs): Ideally this would include a quick handshake that we're not talking
# to some other random process holding the port.
def _check_for_existing_viewer(port: int) -> bool:
try:
# Try opening a connection to the port to see if something is there
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(1)
s.connect(("0.0.0.0", port))
return True
except (socket.timeout, ConnectionRefusedError):
# If the connection times out or is refused, the port is not open
return False
finally:
# Always close the socket to release resources
s.close()


def spawn(
*, port: int = 9876, connect: bool = True, memory_limit: str = "75%", recording: RecordingStream | None = None
*,
port: int = 9876,
connect: bool = True,
memory_limit: str = "75%",
recording: RecordingStream | None = None,
) -> None:
"""
Spawn a Rerun Viewer, listening on the given port.
Expand All @@ -167,7 +189,6 @@ def spawn(
Specifies the [`rerun.RecordingStream`][] to use if `connect = True`.
If left unspecified, defaults to the current active data recording, if there is one.
See also: [`rerun.init`][], [`rerun.set_global_data_recording`][].
"""

import os
Expand All @@ -188,17 +209,32 @@ def spawn(
if python_executable is None:
python_executable = "python3"

# start_new_session=True ensures the spawned process does NOT die when
# we hit ctrl-c in the terminal running the parent Python process.
subprocess.Popen(
[python_executable, "-m", "rerun", f"--port={port}", f"--memory-limit={memory_limit}", "--skip-welcome-screen"],
env=new_env,
start_new_session=True,
)

# TODO(emilk): figure out a way to postpone connecting until the rerun viewer is listening.
# For example, wait until it prints "Hosting a SDK server over TCP at …"
sleep(0.5) # almost as good as waiting the correct amount of time
# TODO(jleibs): More options to opt out of this behavior.
if _check_for_existing_viewer(port):
# Using print here for now rather than `logging.info` because logging.info isn't
# visible by default.
#
# If we spawn a process it's going to send a bunch of stuff to stdout anyways.
print(f"Found existing process on port {port}. Trying to connect.")
else:
# start_new_session=True ensures the spawned process does NOT die when
# we hit ctrl-c in the terminal running the parent Python process.
subprocess.Popen(
[
python_executable,
"-m",
"rerun",
f"--port={port}",
f"--memory-limit={memory_limit}",
"--skip-welcome-screen",
],
env=new_env,
start_new_session=True,
)

# TODO(emilk): figure out a way to postpone connecting until the rerun viewer is listening.
# For example, wait until it prints "Hosting a SDK server over TCP at …"
sleep(0.5) # almost as good as waiting the correct amount of time

if connect:
_connect(f"127.0.0.1:{port}", recording=recording)

0 comments on commit 8c910aa

Please sign in to comment.