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