Skip to content

Commit dcd07bf

Browse files
authored
Merge branch 'main' into libsql-transactions
2 parents 8b1c490 + 18aad3a commit dcd07bf

31 files changed

Lines changed: 410 additions & 151 deletions

File tree

.github/release-drafter.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
name-template: 'v$RESOLVED_VERSION'
2-
tag-template: 'v$RESOLVED_VERSION'
1+
name-template: 'SQLD v$RESOLVED_VERSION'
2+
tag-template: 'libsql-server-v$RESOLVED_VERSION'
3+
tag-prefix: "libsql-server-v"
4+
include-paths: ["libsql-server"]
35
categories:
46
- title: '🚀 Features'
57
labels:

.github/workflows/nemesis.yml

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
name: Nemesis Tests
22

33
on:
4-
push:
5-
branches: [ "main" ]
6-
pull_request_target:
7-
branches: [ "main" ]
8-
merge_group:
9-
branches: [ "main" ]
4+
schedule:
5+
- cron: '* */4 * * *'
106

117
env:
128
CARGO_TERM_COLOR: always
@@ -16,17 +12,8 @@ env:
1612
# RUSTFLAGS: "-D warnings"
1713

1814
jobs:
19-
authorize:
20-
environment: ${{ github.event_name == 'pull_request_target' &&
21-
github.event.pull_request.head.repo.full_name != github.repository &&
22-
'external' || 'internal' }}
23-
runs-on: ubuntu-latest
24-
steps:
25-
- run: true
26-
2715
test-nemesis:
28-
needs: authorize
29-
runs-on: self-hosted
16+
runs-on: ubuntu-latest
3017
name: Run Nemesis Tests
3118
env:
3219
RUSTFLAGS: -D warnings
@@ -59,14 +46,14 @@ jobs:
5946
- name: Cargo build
6047
run: |
6148
cargo build
62-
mv ./target/debug/sqld /home/ubuntu/.cargo/bin
49+
mv ./target/debug/sqld ~/.cargo/bin
6350
sqld --version
6451
6552
- name: Download MinIO binary
6653
run: |
6754
wget -q https://dl.min.io/server/minio/release/linux-amd64/minio -O minio
6855
chmod +x minio
69-
mv minio /home/ubuntu/.cargo/bin
56+
mv minio ~/.cargo/bin
7057
minio --version
7158
7259
- name: Nemesis tests checkout

Cargo.lock

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ installers = ["shell", "homebrew"]
4747
# A GitHub repo to push Homebrew formulas to
4848
tap = "tursodatabase/homebrew-sqld"
4949
# Target platforms to build apps for (Rust target-triple syntax)
50-
targets = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-musl"]
50+
targets = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-apple-darwin"]
5151
# Publish jobs to run in CI
5252
publish-jobs = ["homebrew"]
5353
# Whether cargo-dist should create a Github Release or use an existing draft
54-
create-release = false
54+
create-release = true
5555
# Publish jobs to run in CI
5656
pr-run-mode = "plan"
5757
allow-dirty = ["ci"] # we've edited the release yml to narrow the tag scope

bottomless-cli/src/main.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,13 @@ async fn run() -> Result<()> {
168168
} else {
169169
options.bucket = std::env::var("LIBSQL_BOTTOMLESS_BUCKET").ok();
170170
}
171+
172+
if let Some(ns) = options.namespace.as_deref() {
173+
if !ns.starts_with("ns-") {
174+
println!("Namespace should start with 'ns-'");
175+
std::process::exit(1)
176+
}
177+
}
171178
let namespace = options.namespace.as_deref().unwrap_or("ns-default");
172179
std::env::set_var("LIBSQL_BOTTOMLESS_DATABASE_ID", namespace);
173180
let database = match options.database.clone() {

bottomless/src/replicator.rs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub struct Replicator {
4949
/// Last frame which has been confirmed as stored locally outside of WAL file.
5050
/// Always: [last_committed_frame_no] <= [last_sent_frame_no].
5151
last_committed_frame_no: Receiver<Result<u32>>,
52-
flush_trigger: Sender<()>,
52+
flush_trigger: Option<Sender<()>>,
5353
snapshot_waiter: Receiver<Result<Option<Uuid>>>,
5454
snapshot_notifier: Arc<Sender<Result<Option<Uuid>>>>,
5555

@@ -65,7 +65,7 @@ pub struct Replicator {
6565
use_compression: CompressionKind,
6666
max_frames_per_batch: usize,
6767
s3_upload_max_parallelism: usize,
68-
_join_set: JoinSet<()>,
68+
join_set: JoinSet<()>,
6969
}
7070

7171
#[derive(Debug)]
@@ -262,7 +262,7 @@ impl Replicator {
262262
let next_frame_no = Arc::new(AtomicU32::new(1));
263263
let last_sent_frame_no = Arc::new(AtomicU32::new(0));
264264

265-
let mut _join_set = JoinSet::new();
265+
let mut join_set = JoinSet::new();
266266

267267
let (frames_outbox, mut frames_inbox) = tokio::sync::mpsc::channel(64);
268268
let _local_backup = {
@@ -278,7 +278,7 @@ impl Replicator {
278278
let next_frame_no = next_frame_no.clone();
279279
let last_sent_frame_no = last_sent_frame_no.clone();
280280
let batch_interval = options.max_batch_interval;
281-
_join_set.spawn(async move {
281+
join_set.spawn(async move {
282282
loop {
283283
let timeout = Instant::now() + batch_interval;
284284
let trigger = match timeout_at(timeout, flush_trigger_rx.changed()).await {
@@ -313,7 +313,7 @@ impl Replicator {
313313
let client = client.clone();
314314
let bucket = options.bucket_name.clone();
315315
let max_parallelism = options.s3_upload_max_parallelism;
316-
_join_set.spawn(async move {
316+
join_set.spawn(async move {
317317
let sem = Arc::new(tokio::sync::Semaphore::new(max_parallelism));
318318
let mut join_set = JoinSet::new();
319319
while let Some(fdesc) = frames_inbox.recv().await {
@@ -353,7 +353,7 @@ impl Replicator {
353353
generation,
354354
next_frame_no,
355355
last_sent_frame_no,
356-
flush_trigger,
356+
flush_trigger: Some(flush_trigger),
357357
last_committed_frame_no,
358358
verify_crc: options.verify_crc,
359359
db_path,
@@ -365,10 +365,22 @@ impl Replicator {
365365
use_compression: options.use_compression,
366366
max_frames_per_batch: options.max_frames_per_batch,
367367
s3_upload_max_parallelism: options.s3_upload_max_parallelism,
368-
_join_set,
368+
join_set,
369369
})
370370
}
371371

372+
pub async fn shutdown_gracefully(&mut self) -> Result<()> {
373+
let last_frame_no = self.last_known_frame();
374+
// drop flush trigger, which will cause background task for local WAL copier to complete
375+
self.flush_trigger.take();
376+
self.wait_until_committed(last_frame_no).await?;
377+
self.wait_until_snapshotted().await?;
378+
while let Some(t) = self.join_set.join_next().await {
379+
t?;
380+
}
381+
Ok(())
382+
}
383+
372384
pub fn next_frame_no(&self) -> u32 {
373385
self.next_frame_no.load(Ordering::Acquire)
374386
}
@@ -624,8 +636,12 @@ impl Replicator {
624636
}
625637

626638
pub fn request_flush(&self) {
627-
tracing::trace!("Requesting flush");
628-
let _ = self.flush_trigger.send(());
639+
if let Some(tx) = self.flush_trigger.as_ref() {
640+
tracing::trace!("Requesting flush");
641+
let _ = tx.send(());
642+
} else {
643+
tracing::warn!("Cannot request flush - replicator is closing");
644+
}
629645
}
630646

631647
// Drops uncommitted frames newer than given last valid frame

libsql-ffi/bundled/src/sqlite3.c

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14419,16 +14419,6 @@ SQLITE_API void libsql_wasm_engine_free(libsql_wasm_engine_t *);
1441914419
# endif
1442014420
#endif
1442114421

14422-
/*
14423-
** Enable SQLITE_USE_SEH by default on MSVC builds. Only omit
14424-
** SEH support if the -DSQLITE_OMIT_SEH option is given.
14425-
*/
14426-
#if defined(_MSC_VER) && !defined(SQLITE_OMIT_SEH)
14427-
# define SQLITE_USE_SEH 1
14428-
#else
14429-
# undef SQLITE_USE_SEH
14430-
#endif
14431-
1443214422
/*
1443314423
** The SQLITE_THREADSAFE macro must be defined as 0, 1, or 2.
1443414424
** 0 means mutexes are permanently disable and the library is never

libsql-server/src/database.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ impl Database for PrimaryDatabase {
6464
if let Some(replicator) = &self.logger.bottomless_replicator {
6565
let replicator = replicator.lock().unwrap().take();
6666
if let Some(mut replicator) = replicator {
67-
replicator.wait_until_snapshotted().await?;
67+
replicator.shutdown_gracefully().await?;
6868
}
6969
}
7070
Ok(())

libsql-server/src/http/user/mod.rs

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod db_factory;
22
mod dump;
33
mod hrana_over_http_1;
44
mod result_builder;
5+
mod trace;
56
mod types;
67

78
use std::path::Path;
@@ -24,17 +25,16 @@ use serde_json::Number;
2425
use tokio::sync::{mpsc, oneshot, Notify};
2526
use tokio::task::JoinSet;
2627
use tonic::transport::Server;
27-
use tower_http::trace::DefaultOnResponse;
28+
2829
use tower_http::{compression::CompressionLayer, cors};
29-
use tracing::{Level, Span};
3030

3131
use crate::auth::{Auth, Authenticated};
3232
use crate::connection::Connection;
3333
use crate::database::Database;
3434
use crate::error::Error;
3535
use crate::hrana;
3636
use crate::http::user::types::HttpQuery;
37-
use crate::metrics::{CLIENT_VERSION, LEGACY_HTTP_CALL};
37+
use crate::metrics::LEGACY_HTTP_CALL;
3838
use crate::namespace::{MakeNamespace, NamespaceStore};
3939
use crate::net::Accept;
4040
use crate::query::{self, Query};
@@ -308,22 +308,6 @@ where
308308
path: self.path,
309309
};
310310

311-
fn trace_request<B>(req: &Request<B>, span: &Span) {
312-
let _s = span.enter();
313-
314-
tracing::debug!(
315-
"got request: {} {} {:?}",
316-
req.method(),
317-
req.uri(),
318-
req.headers()
319-
);
320-
if let Some(v) = req.headers().get("x-libsql-client-version") {
321-
if let Ok(s) = v.to_str() {
322-
metrics::increment_counter!(CLIENT_VERSION, "version" => s.to_string());
323-
}
324-
}
325-
}
326-
327311
macro_rules! handle_hrana {
328312
($endpoint:expr, $version:expr, $encoding:expr,) => {{
329313
async fn handle_hrana<F: MakeNamespace>(
@@ -420,13 +404,11 @@ where
420404
let router = router
421405
.layer(option_layer(self.idle_shutdown_kicker.clone()))
422406
.layer(
423-
tower_http::trace::TraceLayer::new_for_http()
424-
.on_request(trace_request)
425-
.on_response(
426-
DefaultOnResponse::new()
427-
.level(Level::DEBUG)
428-
.latency_unit(tower_http::LatencyUnit::Micros),
429-
),
407+
tower_http::trace::TraceLayer::new_for_grpc()
408+
.on_eos(trace::eos)
409+
.on_request(trace::request)
410+
.on_response(trace::response)
411+
.on_failure(trace::failure),
430412
)
431413
.layer(CompressionLayer::new())
432414
.layer(
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
use std::time::Duration;
2+
3+
use hyper::{http, HeaderMap, Request, Response};
4+
use tonic::Status;
5+
use tower_http::{
6+
classify::{
7+
ClassifiedResponse, ClassifyResponse, GrpcCode, GrpcErrorsAsFailures, GrpcFailureClass,
8+
},
9+
trace::{DefaultOnResponse, OnResponse},
10+
};
11+
use tracing::{Level, Span};
12+
13+
use crate::metrics::CLIENT_VERSION;
14+
15+
pub(crate) fn request<B>(req: &Request<B>, span: &Span) {
16+
let _s = span.enter();
17+
18+
tracing::debug!(
19+
"got request: {} {} {:?}",
20+
req.method(),
21+
req.uri(),
22+
req.headers()
23+
);
24+
if let Some(v) = req.headers().get("x-libsql-client-version") {
25+
if let Ok(s) = v.to_str() {
26+
metrics::increment_counter!(CLIENT_VERSION, "version" => s.to_string());
27+
}
28+
}
29+
}
30+
31+
pub(crate) fn response<B>(res: &Response<B>, latency: Duration, span: &Span) {
32+
let on_response = DefaultOnResponse::new()
33+
.level(Level::DEBUG)
34+
.latency_unit(tower_http::LatencyUnit::Micros);
35+
36+
let _s = span.enter();
37+
38+
on_response.on_response(res, latency, span);
39+
40+
let is_grpc = res
41+
.headers()
42+
.get(http::header::CONTENT_TYPE)
43+
.map_or(false, |value| {
44+
value.as_bytes().starts_with("application/grpc".as_bytes())
45+
});
46+
47+
if !is_grpc {
48+
let status = res.status();
49+
metrics::increment_counter!(
50+
"libsql_server_user_http_response",
51+
"protocol" => "http",
52+
"status" => status.as_str().to_string()
53+
);
54+
55+
if status.is_server_error() {
56+
metrics::increment_counter!("libsql_server_user_http_fault", "protcol" => "http");
57+
}
58+
} else {
59+
let grpc = GrpcErrorsAsFailures::new().with_success(GrpcCode::FailedPrecondition);
60+
61+
let code = match dbg!(grpc.classify_response(res)) {
62+
ClassifiedResponse::Ready(Ok(())) => "0".to_string(),
63+
ClassifiedResponse::Ready(Err(GrpcFailureClass::Code(code))) => {
64+
metrics::increment_counter!("libsql_server_user_http_fault", "protcol" => "grpc");
65+
code.to_string()
66+
}
67+
ClassifiedResponse::Ready(Err(GrpcFailureClass::Error(_))) => return,
68+
// TODO(lucio): We need to fix this as this is not correct, right now we
69+
// assume that if the grpc-status is not in the init header frame then it will
70+
// be sent in the trailers, in our use case most of our gRPC calls will Error
71+
// initially on response rather than in the end of the stream.
72+
//
73+
// The problem here is for some reason on_eos is not getting called allowing us
74+
// to inspect the trailer headers. So for now we will short cut and treat any
75+
// trailing grpc-status code to be treated as a success.
76+
ClassifiedResponse::RequiresEos(_) => "0".to_string(),
77+
};
78+
79+
metrics::increment_counter!(
80+
"libsql_server_user_http_response",
81+
"protocol" => "grpc",
82+
"status" => code,
83+
);
84+
}
85+
}
86+
87+
pub(crate) fn eos(trailers: Option<&HeaderMap>, _duration: Duration, _span: &Span) {
88+
if let Some(t) = trailers {
89+
if let Some(s) = Status::from_header_map(t) {
90+
let code = s.code().to_string();
91+
92+
metrics::increment_counter!(
93+
"libsql_server_user_http_response",
94+
"protocol" => "grpc",
95+
"status" => code,
96+
);
97+
}
98+
}
99+
}
100+
101+
pub(crate) fn failure<F>(_: F, _: Duration, _: &Span) {
102+
metrics::increment_counter!("libsql_server_user_http_fault", "protcol" => "http");
103+
}

0 commit comments

Comments
 (0)