Skip to content

Unix socket support - #9337

Open
dometto wants to merge 25 commits into
dask:mainfrom
dometto:unix_socket
Open

Unix socket support#9337
dometto wants to merge 25 commits into
dask:mainfrom
dometto:unix_socket

Conversation

@dometto

@dometto dometto commented Jul 27, 2026

Copy link
Copy Markdown

Resolves #3630

This PR adds support for Unix Domain Sockets...

  1. as a protocol/backend in comms
  2. as an optional way to serve the dashboard server

The rationale for this is twofold:

  1. improved security
  2. improved performance

Test this branch with:

  1. the provided tests
  2. or by creating a cluster with the new protocol:
    cluster = LocalCluster(
        n_workers=2,
        protocol="uds",
    )
   # do something with the cluster here

New UDS backend

This is the main part of this PR. We implement a new backend UDS which subclasses the standard TCP classes. When setting protocol on a cluster/worker/scheduler to uds://, this backend is used. When a worker or scheduler is given an absolute path, it will use that absolute path to create the unix socket. If instead any other value is passed in (e.g. the default localhost), the backend will create the socket at a new temporary path.

Benefits

Performance

@mrocklin tested this:

I just benchmarked this on
a macbook as a consideration for Frisky and got a 1.5x speedup (15 GB/s ->
25 GB/s) and 3x latency reduction (18us -> 6us) compared to TCP.  Maybe
still not worth doing there though just due to code complexity and I've yet
to find a workload that's bound by network on localhost there.  Still
pretty cool though.

Security

UDS support is desireable for usecases where:

  • Multi-processing is preferable to multi-threading (so no inproc)
  • ucx is not available (e.g. not on Linux)
  • We care about other users (e.g. on a shared VM) not being able to access our data and processes (so no tcp).
    • within a single node, enabling TLS is not always feasible (in dask-labextension for instance), or desired (more overhead).

Dashboard listening on UDS

When the dashboard_url is set to an absolute path, listen on UDS instead of TCP. This can help prevent new CVE's of the form GHSA-c336-7962-wfj2

Todo

  • Tests added / passed
  • Passes pixi run lint
  • Make unix socket directory configurable
  • Fold tests into general test_comms.py?
  • Use unix:// instead of uds://?

@dometto
dometto requested a review from fjetter as a code owner July 27, 2026 13:07
@dometto dometto changed the title Unix socket Unix socket support Jul 27, 2026
@dometto

dometto commented Jul 27, 2026

Copy link
Copy Markdown
Author

@jacobtomlinson could you let me know if you would be interested in merging this PR (after feedback/refactoring of course)? Thanks!

@jacobtomlinson jacobtomlinson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally seems like a positive change. I agree that I like unix:// more. I would be apprehensive to make this the default without broader community testing, but I agree that it's a better default in the long run.

Could I ask you to make sure CI is happy (unrelated failures are fine) and add docs?

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Unit Test Results

See test report for an extended history of previous test failures. This is useful for diagnosing flaky tests.

    40 files  ±  0      40 suites  ±0   14h 52m 43s ⏱️ + 5m 23s
 4 178 tests + 19   3 998 ✅ + 20    178 💤 ± 0  2 ❌  - 1 
81 320 runs  +382  76 982 ✅ +285  4 336 💤 +99  2 ❌  - 2 

For more details on these failures, see this check.

Results for commit 588d58f. ± Comparison against base commit 40fcd99.

♻️ This comment has been updated with latest results.

Comment thread distributed/node.py Outdated
Comment thread distributed/node.py Outdated
retries_left = 3

if os.path.isabs(http_address["address"]): # unix socket
try: # remove any old sockets

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this make sense or should we do a check on whether the server is bound to unix socket again in stop, and remove there?

@dometto

dometto commented Jul 29, 2026

Copy link
Copy Markdown
Author

I added some docs here and here.

It's pretty short, but the existing documentation on protocols is also pretty basic.

For the dashboard_address which now also supports UDS I couldn't find any docs apart from the function description for LocalCluster().

Let me know if there's anywhere else where I should add any more extensive documentation!

@dometto

dometto commented Jul 29, 2026

Copy link
Copy Markdown
Author

The remaining linting errors are pre-existing.

@dometto
dometto requested a review from jacobtomlinson July 30, 2026 10:42
dometto added 4 commits July 30, 2026 14:51
This is easier than removing on stop. It technically introduces a race condition, but so would removing on stop, I think.
On Linux we need to unset XDG_RUNTIME_DIR when we're not testing it.
Comment thread distributed/comm/tcp.py
Set kernel-level TCP timeout on the stream.
"""
if comm.closed():
if comm.closed() or (not WINDOWS and comm.socket.family is socket.AF_UNIX):

@dometto dometto Jul 30, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that we have to check for unix socket stuff in tcp.py -- that should all belong to the subclass.

To refactor this we could change set_tcp_timeout to a function in the TCP class, create a new UDS subclass of TCP in uds.py, and override set_tcp_timeout there.

An example of this approach is here (see also my other comment): dometto@5e60065

Let me know if you'd like me to implement that.

Comment thread distributed/comm/tcp.py
raise CommClosedError()

return unparse_host_port(*comm.socket.getsockname()[:2])
if not WINDOWS and comm.socket.family is socket.AF_UNIX:

@dometto dometto Jul 30, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that we have to check for unix socket stuff in tcp.py -- that should all belong to the subclass.

Refactoring this is slightly more involved than for set_tcp_tmeout, as this function is called from inside _handle_stream.

To work around this, we would have to implement _handle_stream entirely in UDSListener, instead of making it subclass TCPListener. That is perhaps cleaner anyway.

An example of this approach is again here: dometto@5e60065

Let me know if you'd like me to implement that.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically dometto@5e60065 removes all subclassing from uds.py except for the UDS Comm, which subclasses TCP -- in order not to have to duplicate the code for reading and writing streams.

To make the UDS code entirely independent from TCP another option would be to create a new mixin class for IOStream reading and writing, and include that in both UDS and TCP. Not sure if that's worth it.

@dometto
dometto force-pushed the unix_socket branch 2 times, most recently from 8b9f840 to 0ca84de Compare July 30, 2026 14:10
@dometto

dometto commented Jul 31, 2026

Copy link
Copy Markdown
Author

@jacobtomlinson I think the remaining test failures are probably unrelated.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Investigate UNIX domain sockets

2 participants