Skip to content

Commit 828b130

Browse files
authored
feat(spooler): Implement shutdown behavior in the spooler (#3980)
1 parent 5d351d5 commit 828b130

File tree

11 files changed

+318
-22
lines changed

11 files changed

+318
-22
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
- Remove the `generate-schema` tool. Relay no longer exposes JSON schema for the event protocol. Consult the Rust type documentation of the `relay-event-schema` crate instead. ([#3974](https://github.com/getsentry/relay/pull/3974))
3030
- Allow creation of `SqliteEnvelopeBuffer` from config, and load existing stacks from db on startup. ([#3967](https://github.com/getsentry/relay/pull/3967))
3131
- Only tag `user.geo.subregion` on frontend and mobile projects. ([#4013](https://github.com/getsentry/relay/pull/4013), [#4023](https://github.com/getsentry/relay/pull/4023))
32+
- Implement graceful shutdown mechanism in the `EnvelopeBuffer`. ([#3980](https://github.com/getsentry/relay/pull/3980))
3233

3334
## 24.8.0
3435

relay-server/src/services/buffer/envelope_buffer/mod.rs

+30-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::cmp::Ordering;
22
use std::collections::BTreeSet;
33
use std::convert::Infallible;
44
use std::error::Error;
5+
use std::mem;
56
use std::sync::atomic::AtomicI64;
67
use std::sync::atomic::Ordering as AtomicOrdering;
78
use std::sync::Arc;
@@ -55,9 +56,11 @@ impl PolymorphicEnvelopeBuffer {
5556
memory_checker: MemoryChecker,
5657
) -> Result<Self, EnvelopeBufferError> {
5758
let buffer = if config.spool_envelopes_path().is_some() {
59+
relay_log::trace!("PolymorphicEnvelopeBuffer: initializing sqlite envelope buffer");
5860
let buffer = EnvelopeBuffer::<SqliteStackProvider>::new(config).await?;
5961
Self::Sqlite(buffer)
6062
} else {
63+
relay_log::trace!("PolymorphicEnvelopeBuffer: initializing memory envelope buffer");
6164
let buffer = EnvelopeBuffer::<MemoryStackProvider>::new(memory_checker);
6265
Self::InMemory(buffer)
6366
};
@@ -137,6 +140,20 @@ impl PolymorphicEnvelopeBuffer {
137140
Self::InMemory(buffer) => buffer.has_capacity(),
138141
}
139142
}
143+
144+
/// Shuts down the [`PolymorphicEnvelopeBuffer`].
145+
pub async fn shutdown(&mut self) -> bool {
146+
// Currently, we want to flush the buffer only for disk, since the in memory implementation
147+
// tries to not do anything and pop as many elements as possible within the shutdown
148+
// timeout.
149+
let Self::Sqlite(buffer) = self else {
150+
relay_log::trace!("PolymorphicEnvelopeBuffer: shutdown procedure not needed");
151+
return false;
152+
};
153+
buffer.flush().await;
154+
155+
true
156+
}
140157
}
141158

142159
/// Error that occurs while interacting with the envelope buffer.
@@ -374,6 +391,19 @@ where
374391
});
375392
}
376393

394+
/// Returns `true` if the underlying storage has the capacity to store more envelopes.
395+
pub fn has_capacity(&self) -> bool {
396+
self.stack_provider.has_store_capacity()
397+
}
398+
399+
/// Flushes the envelope buffer.
400+
pub async fn flush(&mut self) {
401+
let priority_queue = mem::take(&mut self.priority_queue);
402+
self.stack_provider
403+
.flush(priority_queue.into_iter().map(|(q, _)| q.value))
404+
.await;
405+
}
406+
377407
/// Pushes a new [`EnvelopeStack`] with the given [`Envelope`] inserted.
378408
async fn push_stack(
379409
&mut self,
@@ -413,11 +443,6 @@ where
413443
Ok(())
414444
}
415445

416-
/// Returns `true` if the underlying storage has the capacity to store more envelopes.
417-
pub fn has_capacity(&self) -> bool {
418-
self.stack_provider.has_store_capacity()
419-
}
420-
421446
/// Pops an [`EnvelopeStack`] with the supplied [`EnvelopeBufferError`].
422447
fn pop_stack(&mut self, project_key_pair: ProjectKeyPair) {
423448
for project_key in project_key_pair.iter() {

relay-server/src/services/buffer/envelope_stack/memory.rs

+4
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,8 @@ impl EnvelopeStack for MemoryEnvelopeStack {
2828
async fn pop(&mut self) -> Result<Option<Box<Envelope>>, Self::Error> {
2929
Ok(self.0.pop())
3030
}
31+
32+
fn flush(self) -> Vec<Box<Envelope>> {
33+
self.0
34+
}
3135
}

relay-server/src/services/buffer/envelope_stack/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,8 @@ pub trait EnvelopeStack: Send + std::fmt::Debug {
1919

2020
/// Pops the [`Envelope`] on top of the stack.
2121
fn pop(&mut self) -> impl Future<Output = Result<Option<Box<Envelope>>, Self::Error>>;
22+
23+
/// Persists all envelopes in the [`EnvelopeStack`]s to external storage, if possible,
24+
/// and consumes the stack provider.
25+
fn flush(self) -> Vec<Box<Envelope>>;
2226
}

relay-server/src/services/buffer/envelope_stack/sqlite.rs

+32
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,10 @@ impl EnvelopeStack for SqliteEnvelopeStack {
234234

235235
Ok(result)
236236
}
237+
238+
fn flush(self) -> Vec<Box<Envelope>> {
239+
self.batches_buffer.into_iter().flatten().collect()
240+
}
237241
}
238242

239243
#[cfg(test)]
@@ -461,4 +465,32 @@ mod tests {
461465
}
462466
assert_eq!(stack.batches_buffer_size, 0);
463467
}
468+
469+
#[tokio::test]
470+
async fn test_drain() {
471+
let db = setup_db(true).await;
472+
let envelope_store = SqliteEnvelopeStore::new(db, Duration::from_millis(100));
473+
let mut stack = SqliteEnvelopeStack::new(
474+
envelope_store.clone(),
475+
5,
476+
1,
477+
ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap(),
478+
ProjectKey::parse("b81ae32be2584e0bbd7a4cbb95971fe1").unwrap(),
479+
true,
480+
);
481+
482+
let envelopes = mock_envelopes(5);
483+
484+
// We push 5 envelopes and check that there is nothing on disk.
485+
for envelope in envelopes.clone() {
486+
assert!(stack.push(envelope).await.is_ok());
487+
}
488+
assert_eq!(stack.batches_buffer_size, 5);
489+
assert_eq!(envelope_store.total_count().await.unwrap(), 0);
490+
491+
// We drain the stack and make sure nothing was spooled to disk.
492+
let drained_envelopes = stack.flush();
493+
assert_eq!(drained_envelopes.into_iter().collect::<Vec<_>>().len(), 5);
494+
assert_eq!(envelope_store.total_count().await.unwrap(), 0);
495+
}
464496
}

relay-server/src/services/buffer/mod.rs

+56-13
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
//! Types for buffering envelopes.
22
3+
use std::error::Error;
34
use std::sync::atomic::AtomicBool;
45
use std::sync::atomic::Ordering;
56
use std::sync::Arc;
67
use std::time::Duration;
78

89
use relay_base_schema::project::ProjectKey;
910
use relay_config::Config;
10-
use relay_system::Request;
1111
use relay_system::SendError;
1212
use relay_system::{Addr, FromMessage, Interface, NoResponse, Receiver, Service};
13+
use relay_system::{Controller, Request, Shutdown};
1314
use tokio::sync::watch;
15+
use tokio::time::timeout;
1416

1517
use crate::envelope::Envelope;
1618
use crate::services::buffer::envelope_buffer::Peek;
@@ -208,18 +210,18 @@ impl EnvelopeBufferService {
208210
&mut self,
209211
buffer: &mut PolymorphicEnvelopeBuffer,
210212
) -> Result<(), EnvelopeBufferError> {
211-
relay_log::trace!("EnvelopeBufferService peek");
213+
relay_log::trace!("EnvelopeBufferService: peeking the buffer");
212214
match buffer.peek().await? {
213215
Peek::Empty => {
214-
relay_log::trace!("EnvelopeBufferService empty");
216+
relay_log::trace!("EnvelopeBufferService: peek returned empty");
215217
relay_statsd::metric!(
216218
counter(RelayCounters::BufferTryPop) += 1,
217219
peek_result = "empty"
218220
);
219221
self.sleep = Duration::MAX; // wait for reset by `handle_message`.
220222
}
221223
Peek::Ready(_) => {
222-
relay_log::trace!("EnvelopeBufferService pop");
224+
relay_log::trace!("EnvelopeBufferService: popping envelope");
223225
relay_statsd::metric!(
224226
counter(RelayCounters::BufferTryPop) += 1,
225227
peek_result = "ready"
@@ -234,7 +236,7 @@ impl EnvelopeBufferService {
234236
self.sleep = Duration::ZERO; // try next pop immediately
235237
}
236238
Peek::NotReady(stack_key, envelope) => {
237-
relay_log::trace!("EnvelopeBufferService request update");
239+
relay_log::trace!("EnvelopeBufferService: project(s) of envelope not ready, requesting project update");
238240
relay_statsd::metric!(
239241
counter(RelayCounters::BufferTryPop) += 1,
240242
peek_result = "not_ready"
@@ -268,23 +270,55 @@ impl EnvelopeBufferService {
268270
// projects was already triggered (see XXX).
269271
// For better separation of concerns, this prefetch should be triggered from here
270272
// once buffer V1 has been removed.
271-
relay_log::trace!("EnvelopeBufferService push");
273+
relay_log::trace!("EnvelopeBufferService: received push message");
272274
self.push(buffer, envelope).await;
273275
}
274276
EnvelopeBuffer::NotReady(project_key, envelope) => {
275-
relay_log::trace!("EnvelopeBufferService project not ready");
277+
relay_log::trace!(
278+
"EnvelopeBufferService: received project not ready message for project key {}",
279+
&project_key
280+
);
276281
buffer.mark_ready(&project_key, false);
277282
relay_statsd::metric!(counter(RelayCounters::BufferEnvelopesReturned) += 1);
278283
self.push(buffer, envelope).await;
279284
}
280285
EnvelopeBuffer::Ready(project_key) => {
281-
relay_log::trace!("EnvelopeBufferService project ready {}", &project_key);
286+
relay_log::trace!(
287+
"EnvelopeBufferService: received project ready message for project key {}",
288+
&project_key
289+
);
282290
buffer.mark_ready(&project_key, true);
283291
}
284292
};
285293
self.sleep = Duration::ZERO;
286294
}
287295

296+
async fn handle_shutdown(
297+
&mut self,
298+
buffer: &mut PolymorphicEnvelopeBuffer,
299+
message: Shutdown,
300+
) -> bool {
301+
// We gracefully shut down only if the shutdown has a timeout.
302+
if let Some(shutdown_timeout) = message.timeout {
303+
relay_log::trace!("EnvelopeBufferService: shutting down gracefully");
304+
305+
let shutdown_result = timeout(shutdown_timeout, buffer.shutdown()).await;
306+
match shutdown_result {
307+
Ok(shutdown_result) => {
308+
return shutdown_result;
309+
}
310+
Err(error) => {
311+
relay_log::error!(
312+
error = &error as &dyn Error,
313+
"the envelope buffer didn't shut down in time, some envelopes might be lost",
314+
);
315+
}
316+
}
317+
}
318+
319+
false
320+
}
321+
288322
async fn push(&mut self, buffer: &mut PolymorphicEnvelopeBuffer, envelope: Box<Envelope>) {
289323
if let Err(e) = buffer.push(envelope).await {
290324
relay_log::error!(
@@ -322,15 +356,17 @@ impl Service for EnvelopeBufferService {
322356
};
323357
buffer.initialize().await;
324358

325-
relay_log::info!("EnvelopeBufferService start");
359+
let mut shutdown = Controller::shutdown_handle();
360+
361+
relay_log::info!("EnvelopeBufferService: starting");
326362
let mut iteration = 0;
327363
loop {
328364
iteration += 1;
329-
relay_log::trace!("EnvelopeBufferService loop iteration {iteration}");
365+
relay_log::trace!("EnvelopeBufferService: loop iteration {iteration}");
330366

331367
tokio::select! {
332368
// NOTE: we do not select a bias here.
333-
// On the one hand, we might want to prioritize dequeing over enqueing
369+
// On the one hand, we might want to prioritize dequeuing over enqueuing
334370
// so we do not exceed the buffer capacity by starving the dequeue.
335371
// on the other hand, prioritizing old messages violates the LIFO design.
336372
Ok(()) = self.ready_to_pop(&mut buffer) => {
@@ -344,8 +380,15 @@ impl Service for EnvelopeBufferService {
344380
Some(message) = rx.recv() => {
345381
self.handle_message(&mut buffer, message).await;
346382
}
383+
shutdown = shutdown.notified() => {
384+
// In case the shutdown was handled, we break out of the loop signaling that
385+
// there is no need to process anymore envelopes.
386+
if self.handle_shutdown(&mut buffer, shutdown).await {
387+
break;
388+
}
389+
}
347390
_ = global_config_rx.changed() => {
348-
relay_log::trace!("EnvelopeBufferService received global config");
391+
relay_log::trace!("EnvelopeBufferService: received global config");
349392
self.sleep = Duration::ZERO; // Try to pop
350393
}
351394
else => break,
@@ -354,7 +397,7 @@ impl Service for EnvelopeBufferService {
354397
self.update_observable_state(&mut buffer);
355398
}
356399

357-
relay_log::info!("EnvelopeBufferService stop");
400+
relay_log::info!("EnvelopeBufferService: stopping");
358401
});
359402
}
360403
}

relay-server/src/services/buffer/stack_provider/memory.rs

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::services::buffer::stack_provider::{
44
InitializationState, StackCreationType, StackProvider,
55
};
66
use crate::utils::MemoryChecker;
7+
use crate::EnvelopeStack;
78

89
#[derive(Debug)]
910
pub struct MemoryStackProvider {
@@ -41,4 +42,11 @@ impl StackProvider for MemoryStackProvider {
4142
fn stack_type<'a>(&self) -> &'a str {
4243
"memory"
4344
}
45+
46+
async fn flush(&mut self, envelope_stacks: impl IntoIterator<Item = Self::Stack>) {
47+
for envelope_stack in envelope_stacks {
48+
// The flushed envelopes will be immediately dropped.
49+
let _ = envelope_stack.flush();
50+
}
51+
}
4452
}

relay-server/src/services/buffer/stack_provider/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,10 @@ pub trait StackProvider: std::fmt::Debug {
6060

6161
/// Returns the string representation of the stack type offered by this [`StackProvider`].
6262
fn stack_type<'a>(&self) -> &'a str;
63+
64+
/// Flushes the supplied [`EnvelopeStack`]s and consumes the [`StackProvider`].
65+
fn flush(
66+
&mut self,
67+
envelope_stacks: impl IntoIterator<Item = Self::Stack>,
68+
) -> impl Future<Output = ()>;
6369
}

0 commit comments

Comments
 (0)