Skip to content

Commit 3c5d940

Browse files
acatangiuJonathanWoollett-Light
authored andcommitted
Add kill switch functionality
Added shutdown event-fd acting as a kill switch actionable from outside the micro-http server. Needed to be able to break out of the inner epoll_wait on demand. This can be used to signal the micro-http server running on a dedicated thread that it needs to shut down. Co-authored-by: acatangiu <[email protected]> Signed-off-by: Egor Lazarchuk <[email protected]>
1 parent b538bf8 commit 3c5d940

File tree

2 files changed

+81
-15
lines changed

2 files changed

+81
-15
lines changed

src/common/mod.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,14 @@ pub enum ServerError {
166166
ConnectionError(ConnectionError),
167167
/// Epoll operations failed.
168168
IOError(std::io::Error),
169-
/// Overflow occured while processing messages.
169+
/// Overflow occurred while processing messages.
170170
Overflow,
171171
/// Server maximum capacity has been reached.
172172
ServerFull,
173-
/// Underflow occured while processing mesagges.
173+
/// Underflow occurred while processing messages.
174174
Underflow,
175+
/// Shutdown requested.
176+
ShutdownEvent,
175177
}
176178

177179
impl Display for ServerError {
@@ -182,6 +184,7 @@ impl Display for ServerError {
182184
Self::Overflow => write!(f, "Overflow occured while processing messages."),
183185
Self::ServerFull => write!(f, "Server is full."),
184186
Self::Underflow => write!(f, "Underflow occured while processing messages."),
187+
Self::ShutdownEvent => write!(f, "Shutdown requested."),
185188
}
186189
}
187190
}

src/server.rs

+76-13
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ pub use crate::common::{ConnectionError, RequestError, ServerError};
1212
use crate::connection::HttpConnection;
1313
use crate::request::Request;
1414
use crate::response::{Response, StatusCode};
15-
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
16-
17-
use vmm_sys_util::epoll;
15+
use vmm_sys_util::{epoll, eventfd::EventFd, sock_ctrl_msg::ScmSocket};
1816

1917
static SERVER_FULL_ERROR_MESSAGE: &[u8] = b"HTTP/1.1 503\r\n\
2018
Server: Firecracker API\r\n\
@@ -27,6 +25,7 @@ pub(crate) const MAX_PAYLOAD_SIZE: usize = 51200;
2725
type Result<T> = std::result::Result<T, ServerError>;
2826

2927
/// Wrapper over `Request` which adds an identification token.
28+
#[derive(Debug)]
3029
pub struct ServerRequest {
3130
/// Inner request.
3231
pub request: Request,
@@ -255,6 +254,9 @@ pub struct HttpServer {
255254
socket: UnixListener,
256255
/// Server's epoll instance.
257256
epoll: epoll::Epoll,
257+
/// Event requesting micro-http shutdown.
258+
/// Used to break out of inner `epoll_wait` and reports shutdown event.
259+
kill_switch: Option<EventFd>,
258260
/// Holds the token-connection pairs of the server.
259261
/// Each connection has an associated identification token, which is
260262
/// the file descriptor of the underlying stream.
@@ -278,6 +280,7 @@ impl HttpServer {
278280
Ok(Self {
279281
socket,
280282
epoll,
283+
kill_switch: None,
281284
connections: HashMap::new(),
282285
payload_max_size: MAX_PAYLOAD_SIZE,
283286
})
@@ -300,11 +303,21 @@ impl HttpServer {
300303
Ok(HttpServer {
301304
socket,
302305
epoll,
306+
kill_switch: None,
303307
connections: HashMap::new(),
304308
payload_max_size: MAX_PAYLOAD_SIZE,
305309
})
306310
}
307311

312+
/// Adds a `kill_switch` event fd used to break out of inner `epoll_wait`
313+
/// and report a shutdown event.
314+
pub fn add_kill_switch(&mut self, kill_switch: EventFd) -> Result<()> {
315+
// Add the kill switch to the `epoll` structure.
316+
let ret = Self::epoll_add(&self.epoll, kill_switch.as_raw_fd());
317+
self.kill_switch = Some(kill_switch);
318+
ret
319+
}
320+
308321
/// This function sets the limit for PUT/PATCH requests. It overwrites the
309322
/// default limit of 0.05MiB with the one allowed by server.
310323
pub fn set_payload_max_size(&mut self, request_payload_max_size: usize) {
@@ -337,25 +350,33 @@ impl HttpServer {
337350
/// on a connection on which it is not possible.
338351
pub fn requests(&mut self) -> Result<Vec<ServerRequest>> {
339352
let mut parsed_requests: Vec<ServerRequest> = vec![];
340-
let mut events = vec![epoll::EpollEvent::default(); MAX_CONNECTIONS];
353+
// Possible events coming from FDs: 1 + 1 + MAX_CONNECTIONS:
354+
// exit-eventfd, sock-listen-fd, active-connections-fds.
355+
let mut events = [epoll::EpollEvent::default(); MAX_CONNECTIONS + 2];
341356
// This is a wrapper over the syscall `epoll_wait` and it will block the
342357
// current thread until at least one event is received.
343358
// The received notifications will then populate the `events` array with
344-
// `event_count` elements, where 1 <= event_count <= MAX_CONNECTIONS.
359+
// `event_count` elements, where 1 <= event_count <= MAX_CONNECTIONS + 2.
345360
let event_count = match self.epoll.wait(-1, &mut events[..]) {
346361
Ok(event_count) => event_count,
347362
Err(e) if e.raw_os_error() == Some(libc::EINTR) => 0,
348363
Err(e) => return Err(ServerError::IOError(e)),
349364
};
350-
// We use `take()` on the iterator over `events` as, even though only
351-
// `events_count` events have been inserted into `events`, the size of
352-
// the array is still `MAX_CONNECTIONS`, so we discard empty elements
365+
366+
// Getting the file descriptor for kill switch.
367+
// If there is no kill switch fd, we use value -1 as an invalid fd.
368+
let kill_fd = self.kill_switch.as_ref().map_or(-1, |ks| ks.as_raw_fd());
369+
370+
// We only iterate over first `event_count` events and discard empty elements
353371
// at the end of the array.
354-
for e in events.iter().take(event_count) {
372+
for e in events[..event_count].iter() {
355373
// Check the file descriptor which produced the notification `e`.
356-
// It could be that we have a new connection, or one of our open
357-
// connections is ready to exchange data with a client.
358-
if e.fd() == self.socket.as_raw_fd() {
374+
// It could be that we need to shutdown, or have a new connection, or
375+
// one of our open connections is ready to exchange data with a client.
376+
if e.fd() == kill_fd {
377+
// Report that the kill switch was triggered.
378+
return Err(ServerError::ShutdownEvent);
379+
} else if e.fd() == self.socket.as_raw_fd() {
359380
// We have received a notification on the listener socket, which
360381
// means we have a new connection to accept.
361382
match self.handle_new_connection() {
@@ -646,9 +667,10 @@ mod tests {
646667
use std::io::{Read, Write};
647668
use std::net::Shutdown;
648669
use std::os::unix::net::UnixStream;
670+
use std::sync::{Arc, Mutex};
649671

650672
use crate::common::Body;
651-
use vmm_sys_util::tempfile::TempFile;
673+
use vmm_sys_util::{eventfd::EFD_NONBLOCK, tempfile::TempFile};
652674

653675
fn get_temp_socket_file() -> TempFile {
654676
let mut path_to_socket = TempFile::new().unwrap();
@@ -1095,4 +1117,45 @@ mod tests {
10951117
second_socket.shutdown(std::net::Shutdown::Both).unwrap();
10961118
assert!(server.requests().is_ok());
10971119
}
1120+
1121+
#[test]
1122+
fn test_kill_switch() {
1123+
let path_to_socket = get_temp_socket_file();
1124+
1125+
let mut server = HttpServer::new(path_to_socket.as_path()).unwrap();
1126+
let kill_switch = EventFd::new(EFD_NONBLOCK).unwrap();
1127+
server
1128+
.add_kill_switch(kill_switch.try_clone().unwrap())
1129+
.unwrap();
1130+
server.start_server().unwrap();
1131+
1132+
let request_result = Arc::new(Mutex::new(Ok(vec![])));
1133+
let res_clone = request_result.clone();
1134+
// Start a thread running the server, expect it to report shutdown event.
1135+
let handler = std::thread::spawn(move || {
1136+
*res_clone.lock().unwrap() = server.requests();
1137+
});
1138+
1139+
// Trigger kill switch.
1140+
kill_switch.write(1).unwrap();
1141+
// Then send request.
1142+
let mut socket = UnixStream::connect(path_to_socket.as_path()).unwrap();
1143+
socket
1144+
.write_all(
1145+
b"PATCH /machine-config HTTP/1.1\r\n\
1146+
Content-Length: 13\r\n\
1147+
Content-Type: application/json\r\n\r\nwhatever body",
1148+
)
1149+
.unwrap();
1150+
// Wait for server thread to handle events.
1151+
handler.join().unwrap();
1152+
1153+
// Expect shutdown event instead of http request event.
1154+
match &*request_result.lock().unwrap() {
1155+
Err(ServerError::ShutdownEvent) => (),
1156+
v => {
1157+
panic!("Expected shutdown event, instead got {:?}.", v)
1158+
}
1159+
};
1160+
}
10981161
}

0 commit comments

Comments
 (0)