only_instance.rs

 1use std::{
 2    io::{Read, Write},
 3    net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener, TcpStream},
 4    thread,
 5    time::Duration,
 6};
 7
 8use util::channel::ReleaseChannel;
 9
10const LOCALHOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
11const CONNECT_TIMEOUT: Duration = Duration::from_millis(10);
12const RECEIVE_TIMEOUT: Duration = Duration::from_millis(35);
13const SEND_TIMEOUT: Duration = Duration::from_millis(20);
14
15fn address() -> SocketAddr {
16    let port = match *util::channel::RELEASE_CHANNEL {
17        ReleaseChannel::Dev => 43737,
18        ReleaseChannel::Preview => 43738,
19        ReleaseChannel::Stable => 43739,
20    };
21
22    SocketAddr::V4(SocketAddrV4::new(LOCALHOST, port))
23}
24
25fn instance_handshake() -> &'static str {
26    match *util::channel::RELEASE_CHANNEL {
27        ReleaseChannel::Dev => "Zed Editor Dev Instance Running",
28        ReleaseChannel::Preview => "Zed Editor Preview Instance Running",
29        ReleaseChannel::Stable => "Zed Editor Stable Instance Running",
30    }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum IsOnlyInstance {
35    Yes,
36    No,
37}
38
39pub fn ensure_only_instance() -> IsOnlyInstance {
40    if check_got_handshake() {
41        return IsOnlyInstance::No;
42    }
43
44    let listener = match TcpListener::bind(address()) {
45        Ok(listener) => listener,
46
47        Err(err) => {
48            log::warn!("Error binding to single instance port: {err}");
49            if check_got_handshake() {
50                return IsOnlyInstance::No;
51            }
52
53            // Avoid failing to start when some other application by chance already has
54            // a claim on the port. This is sub-par as any other instance that gets launched
55            // will be unable to communicate with this instance and will duplicate
56            log::warn!("Backup handshake request failed, continuing without handshake");
57            return IsOnlyInstance::Yes;
58        }
59    };
60
61    thread::spawn(move || {
62        for stream in listener.incoming() {
63            let mut stream = match stream {
64                Ok(stream) => stream,
65                Err(_) => return,
66            };
67
68            _ = stream.set_nodelay(true);
69            _ = stream.set_read_timeout(Some(SEND_TIMEOUT));
70            _ = stream.write_all(instance_handshake().as_bytes());
71        }
72    });
73
74    IsOnlyInstance::Yes
75}
76
77fn check_got_handshake() -> bool {
78    match TcpStream::connect_timeout(&address(), CONNECT_TIMEOUT) {
79        Ok(mut stream) => {
80            let mut buf = vec![0u8; instance_handshake().len()];
81
82            stream.set_read_timeout(Some(RECEIVE_TIMEOUT)).unwrap();
83            if let Err(err) = stream.read_exact(&mut buf) {
84                log::warn!("Connected to single instance port but failed to read: {err}");
85                return false;
86            }
87
88            if buf == instance_handshake().as_bytes() {
89                log::info!("Got instance handshake");
90                return true;
91            }
92
93            log::warn!("Got wrong instance handshake value");
94            false
95        }
96
97        Err(_) => false,
98    }
99}