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        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}