Allow Zed to run under multiple user accounts simultaneously (#14143)

Closes #4607

This is an attempt to enable Zed to run under multiple user accounts on
the same Mac, because it's a blocker to me really giving Zed a fair shot
at being my primary editor.

According to some helpful info from @ForLoveOfCats in #4607 the main
reason why this doesn't work is because Zed is using a Unix socket or
maybe a TCP socket with a hard-coded path and/or port. To me it looks
like it's a TCP socket so I tried changing that code in here, but I'm
stuck at trying to test it out because running `target/debug/zed` or
`target/release/zed` seems to behave differently than running an actual
app bundle. I had no luck copying the binary over to
/Applications/Zed.app/Contents/MacOS/zed because it can't find
WebRTC.framework which resides at a different relative path in the app
bundle.

If this seems like a desirable change to the core team then I'm looking
for some guidance on how to build an app bundle or otherwise test out
this change, or a nudge in the correct direction if I'm way off base
with my current approach.

Release Notes:

- Added multiuser support for up to 100 users on the same machine.

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
Sami Samhuri 2024-08-31 17:46:14 -07:00 committed by GitHub
parent 837639535c
commit b386b6c237
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 54 additions and 4 deletions

1
Cargo.lock generated
View file

@ -14260,6 +14260,7 @@ dependencies = [
"smol", "smol",
"snippet_provider", "snippet_provider",
"supermaven", "supermaven",
"sysinfo",
"tab_switcher", "tab_switcher",
"task", "task",
"tasks_ui", "tasks_ui",

View file

@ -113,6 +113,7 @@ vim.workspace = true
welcome.workspace = true welcome.workspace = true
workspace.workspace = true workspace.workspace = true
zed_actions.workspace = true zed_actions.workspace = true
sysinfo.workspace = true
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
windows.workspace = true windows.workspace = true

View file

@ -5,22 +5,70 @@ use std::{
time::Duration, time::Duration,
}; };
use sysinfo::System;
use release_channel::ReleaseChannel; use release_channel::ReleaseChannel;
const LOCALHOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1); const LOCALHOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
const CONNECT_TIMEOUT: Duration = Duration::from_millis(10); const CONNECT_TIMEOUT: Duration = Duration::from_millis(10);
const RECEIVE_TIMEOUT: Duration = Duration::from_millis(35); const RECEIVE_TIMEOUT: Duration = Duration::from_millis(35);
const SEND_TIMEOUT: Duration = Duration::from_millis(20); const SEND_TIMEOUT: Duration = Duration::from_millis(20);
const USER_BLOCK: u16 = 100;
fn address() -> SocketAddr { fn address() -> SocketAddr {
// These port numbers are offset by the user ID to avoid conflicts between
// different users on the same machine. In addition to that the ports for each
// release channel are spaced out by 100 to avoid conflicts between different
// users running different release channels on the same machine. This ends up
// interleaving the ports between different users and different release channels.
//
// On macOS user IDs start at 501 and on Linux they start at 1000. The first user
// on a Mac with ID 501 running a dev channel build will use port 44238, and the
// second user with ID 502 will use port 44239, and so on. User 501 will use ports
// 44338, 44438, and 44538 for the preview, stable, and nightly channels,
// respectively. User 502 will use ports 44339, 44439, and 44539 for the preview,
// stable, and nightly channels, respectively.
let port = match *release_channel::RELEASE_CHANNEL { let port = match *release_channel::RELEASE_CHANNEL {
ReleaseChannel::Dev => 43737, ReleaseChannel::Dev => 43737,
ReleaseChannel::Preview => 43738, ReleaseChannel::Preview => 43737 + USER_BLOCK,
ReleaseChannel::Stable => 43739, ReleaseChannel::Stable => 43737 + (2 * USER_BLOCK),
ReleaseChannel::Nightly => 43740, ReleaseChannel::Nightly => 43737 + (3 * USER_BLOCK),
}; };
let mut user_port = port;
let mut sys = System::new_all();
sys.refresh_all();
if let Ok(current_pid) = sysinfo::get_current_pid() {
if let Some(uid) = sys
.process(current_pid)
.and_then(|process| process.user_id())
{
let uid_u32 = get_uid_as_u32(uid);
// Ensure that the user ID is not too large to avoid overflow when
// calculating the port number. This seems unlikely but it doesn't
// hurt to be safe.
let max_port = 65535;
let max_uid: u32 = max_port - port as u32;
let wrapped_uid: u16 = (uid_u32 % max_uid) as u16;
user_port += wrapped_uid;
}
}
SocketAddr::V4(SocketAddrV4::new(LOCALHOST, port)) SocketAddr::V4(SocketAddrV4::new(LOCALHOST, user_port))
}
#[cfg(unix)]
fn get_uid_as_u32(uid: &sysinfo::Uid) -> u32 {
*uid.clone()
}
#[cfg(windows)]
fn get_uid_as_u32(uid: &sysinfo::Uid) -> u32 {
// Extract the RID which is an integer
uid.to_string()
.rsplit('-')
.next()
.and_then(|rid| rid.parse::<u32>().ok())
.unwrap_or(0)
} }
fn instance_handshake() -> &'static str { fn instance_handshake() -> &'static str {