@@ -233,34 +233,54 @@ impl CrashHarness {
233233 self . wait_ready ( Duration :: from_secs ( 20 ) ) ;
234234 }
235235
236+ /// Open a fresh pgwire connection, run one statement, and return the
237+ /// resulting messages. Panics on connect/exec error.
238+ ///
239+ /// Retries the transient "no sequencer leader elected yet" startup
240+ /// condition: `/healthz` intentionally reports ready before the Calvin
241+ /// sequencer group has elected a leader (the sequencer is deliberately not
242+ /// a data group in the readiness gate), so a cross-shard write issued in
243+ /// the first moments of uptime can race the election and get a clean,
244+ /// retryable error. On a loaded machine that window is wide enough to lose.
245+ /// A real client retries; so does the harness, bounded, before writing.
246+ async fn simple_query_ready ( & self , sql : & str ) -> Vec < tokio_postgres:: SimpleQueryMessage > {
247+ let deadline = Instant :: now ( ) + Duration :: from_secs ( 20 ) ;
248+ loop {
249+ let ( client, connection) =
250+ tokio_postgres:: connect ( & self . pgwire_conn_str ( ) , tokio_postgres:: NoTls )
251+ . await
252+ . expect ( "connect for exec" ) ;
253+ let conn_handle = tokio:: spawn ( async move {
254+ let _ = connection. await ;
255+ } ) ;
256+ let result = client. simple_query ( sql) . await ;
257+ drop ( client) ;
258+ let _ = conn_handle. await ;
259+ match result {
260+ Ok ( messages) => return messages,
261+ Err ( e)
262+ if Instant :: now ( ) < deadline
263+ && e. as_db_error ( ) . is_some_and ( |db| {
264+ db. message ( ) . contains ( "no sequencer leader elected yet" )
265+ } ) =>
266+ {
267+ tokio:: time:: sleep ( Duration :: from_millis ( 100 ) ) . await ;
268+ }
269+ Err ( e) => panic ! ( "exec: {e}" ) ,
270+ }
271+ }
272+ }
273+
236274 /// Open a fresh pgwire connection, run one statement, and drop the
237275 /// connection. Panics on connect/exec error.
238276 pub async fn exec ( & self , sql : & str ) {
239- let ( client, connection) =
240- tokio_postgres:: connect ( & self . pgwire_conn_str ( ) , tokio_postgres:: NoTls )
241- . await
242- . expect ( "connect for exec" ) ;
243- let conn_handle = tokio:: spawn ( async move {
244- let _ = connection. await ;
245- } ) ;
246- client. simple_query ( sql) . await . expect ( "exec" ) ;
247- drop ( client) ;
248- let _ = conn_handle. await ;
277+ let _ = self . simple_query_ready ( sql) . await ;
249278 }
250279
251280 /// Run a query and return column `col` from every returned row, as text
252281 /// (via `simple_query`, so the value survives regardless of its type OID).
253282 pub async fn query_col ( & self , sql : & str , col : & str ) -> Vec < String > {
254- let ( client, connection) =
255- tokio_postgres:: connect ( & self . pgwire_conn_str ( ) , tokio_postgres:: NoTls )
256- . await
257- . expect ( "connect for query" ) ;
258- let conn_handle = tokio:: spawn ( async move {
259- let _ = connection. await ;
260- } ) ;
261- let messages = client. simple_query ( sql) . await . expect ( "query" ) ;
262- drop ( client) ;
263- let _ = conn_handle. await ;
283+ let messages = self . simple_query_ready ( sql) . await ;
264284 messages
265285 . iter ( )
266286 . filter_map ( |m| match m {
@@ -278,16 +298,7 @@ impl CrashHarness {
278298 /// `COUNT(*) AS n` — does not surface a usable column name through the
279299 /// pgwire row description, so aggregates must be read by index.
280300 pub async fn query_col_idx ( & self , sql : & str , idx : usize ) -> Vec < String > {
281- let ( client, connection) =
282- tokio_postgres:: connect ( & self . pgwire_conn_str ( ) , tokio_postgres:: NoTls )
283- . await
284- . expect ( "connect for query" ) ;
285- let conn_handle = tokio:: spawn ( async move {
286- let _ = connection. await ;
287- } ) ;
288- let messages = client. simple_query ( sql) . await . expect ( "query" ) ;
289- drop ( client) ;
290- let _ = conn_handle. await ;
301+ let messages = self . simple_query_ready ( sql) . await ;
291302 messages
292303 . iter ( )
293304 . filter_map ( |m| match m {
0 commit comments