105 lines
3.2 KiB
Rust
105 lines
3.2 KiB
Rust
use std::{
|
|
io::{Read, Write},
|
|
net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener, TcpStream},
|
|
thread,
|
|
time::Duration,
|
|
};
|
|
|
|
use util::channel::ReleaseChannel;
|
|
|
|
const LOCALHOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
|
|
const CONNECT_TIMEOUT: Duration = Duration::from_millis(10);
|
|
const RECEIVE_TIMEOUT: Duration = Duration::from_millis(35);
|
|
const SEND_TIMEOUT: Duration = Duration::from_millis(20);
|
|
|
|
fn address() -> SocketAddr {
|
|
let port = match *util::channel::RELEASE_CHANNEL {
|
|
ReleaseChannel::Dev => 43737,
|
|
ReleaseChannel::Preview => 43738,
|
|
ReleaseChannel::Stable => 43739,
|
|
ReleaseChannel::Nightly => 43740,
|
|
};
|
|
|
|
SocketAddr::V4(SocketAddrV4::new(LOCALHOST, port))
|
|
}
|
|
|
|
fn instance_handshake() -> &'static str {
|
|
match *util::channel::RELEASE_CHANNEL {
|
|
ReleaseChannel::Dev => "Zed Editor Dev Instance Running",
|
|
ReleaseChannel::Nightly => "Zed Editor Nightly Instance Running",
|
|
ReleaseChannel::Preview => "Zed Editor Preview Instance Running",
|
|
ReleaseChannel::Stable => "Zed Editor Stable Instance Running",
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum IsOnlyInstance {
|
|
Yes,
|
|
No,
|
|
}
|
|
|
|
pub fn ensure_only_instance() -> IsOnlyInstance {
|
|
if *db::ZED_STATELESS || *util::channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
|
|
return IsOnlyInstance::Yes;
|
|
}
|
|
|
|
if check_got_handshake() {
|
|
return IsOnlyInstance::No;
|
|
}
|
|
|
|
let listener = match TcpListener::bind(address()) {
|
|
Ok(listener) => listener,
|
|
|
|
Err(err) => {
|
|
log::warn!("Error binding to single instance port: {err}");
|
|
if check_got_handshake() {
|
|
return IsOnlyInstance::No;
|
|
}
|
|
|
|
// Avoid failing to start when some other application by chance already has
|
|
// a claim on the port. This is sub-par as any other instance that gets launched
|
|
// will be unable to communicate with this instance and will duplicate
|
|
log::warn!("Backup handshake request failed, continuing without handshake");
|
|
return IsOnlyInstance::Yes;
|
|
}
|
|
};
|
|
|
|
thread::spawn(move || {
|
|
for stream in listener.incoming() {
|
|
let mut stream = match stream {
|
|
Ok(stream) => stream,
|
|
Err(_) => return,
|
|
};
|
|
|
|
_ = stream.set_nodelay(true);
|
|
_ = stream.set_read_timeout(Some(SEND_TIMEOUT));
|
|
_ = stream.write_all(instance_handshake().as_bytes());
|
|
}
|
|
});
|
|
|
|
IsOnlyInstance::Yes
|
|
}
|
|
|
|
fn check_got_handshake() -> bool {
|
|
match TcpStream::connect_timeout(&address(), CONNECT_TIMEOUT) {
|
|
Ok(mut stream) => {
|
|
let mut buf = vec![0u8; instance_handshake().len()];
|
|
|
|
stream.set_read_timeout(Some(RECEIVE_TIMEOUT)).unwrap();
|
|
if let Err(err) = stream.read_exact(&mut buf) {
|
|
log::warn!("Connected to single instance port but failed to read: {err}");
|
|
return false;
|
|
}
|
|
|
|
if buf == instance_handshake().as_bytes() {
|
|
log::info!("Got instance handshake");
|
|
return true;
|
|
}
|
|
|
|
log::warn!("Got wrong instance handshake value");
|
|
false
|
|
}
|
|
|
|
Err(_) => false,
|
|
}
|
|
}
|