@@ -96,44 +96,47 @@ pub struct Client {
9696 service : Option < TrackedService > ,
9797 sidecar_settings : Option < protocol:: SidecarSettings > ,
9898 metrics_last_registered : Cell < Option < Instant > > ,
99- req_receiver : Option < mpsc:: Receiver < HelperRequest > > ,
100- req_sender : mpsc:: Sender < HelperRequest > ,
10199}
102100
103101static CLIENT_SERIAL : AtomicU64 = AtomicU64 :: new ( 1 ) ;
104102impl Client {
105103 pub fn new ( service_manager : & ' static ServiceManager ) -> Self {
106- let ( tx, rx) = mpsc:: channel ( 5 ) ;
107104 Self {
108105 id : CLIENT_SERIAL . fetch_add ( 1 , atomic:: Ordering :: Relaxed ) ,
109106 service_manager,
110107 service : None ,
111108 sidecar_settings : None ,
112109 metrics_last_registered : Default :: default ( ) ,
113- req_receiver : Some ( rx) ,
114- req_sender : tx,
115110 }
116111 }
117112
118- pub async fn entrypoint ( self , cancel_token : CancellationToken ) {
113+ pub async fn entrypoint (
114+ self ,
115+ rx : mpsc:: Receiver < HelperRequest > ,
116+ cancel_token : CancellationToken ,
117+ ) {
119118 // wrap entrypoint with the task locals that allow:
120119 // - client id in the logs
121120 // - submission of errors to telemetry (if they happen after client_init)
122121 let client_id = self . id ;
123- let entrypoint_fut = self . do_entrypoint ( cancel_token) ;
122+ let entrypoint_fut = self . do_entrypoint ( rx , cancel_token) ;
124123 log:: with_scoped_client_id ( client_id, with_error_telemetry_handle ( entrypoint_fut) ) . await ;
125124 }
126125
127- async fn do_entrypoint ( mut self , cancel_token : CancellationToken ) {
126+ async fn do_entrypoint (
127+ self ,
128+ rx : mpsc:: Receiver < HelperRequest > ,
129+ cancel_token : CancellationToken ,
130+ ) {
128131 info ! ( "starting" ) ;
129132
130- let res = do_client_entrypoint ( & mut self , cancel_token) . await ;
133+ let res = do_client_entrypoint ( self , rx , cancel_token) . await ;
131134 match res {
132135 Ok ( _) => {
133136 info ! ( "ended normally" ) ;
134137 }
135138 Err ( err) if err. is :: < ForcefulDisconnect > ( ) => {
136- warn ! ( "ended due to client connectivity issue: {}" , err) ;
139+ info ! ( "ended due to client connectivity issue: {:# }" , err) ;
137140 }
138141 Err ( err) => {
139142 error ! ( "ended with failure: {:#}" , err) ;
@@ -145,53 +148,21 @@ impl Client {
145148 pub fn get_service ( & self ) -> & Service {
146149 self . service . as_ref ( ) . expect ( "service not initialized" )
147150 }
148-
149- pub fn get_req_sender ( & self ) -> mpsc:: Sender < HelperRequest > {
150- self . req_sender . clone ( )
151- }
152-
153- fn req_stream ( & mut self ) -> CommandStream {
154- let receiver = std:: mem:: take ( & mut self . req_receiver ) . unwrap ( ) ;
155- let cmd_stream = ReceiverStream :: new ( receiver) ;
156- let cmd_stream = StreamExt :: map ( cmd_stream, |msg| {
157- let mut codec = protocol:: CommandCodec ;
158- (
159- codec. decode_eof ( & mut BytesMut :: from ( msg. command ) ) ,
160- msg. response_tx ,
161- )
162- } ) ;
163- let cmd_stream = cmd_stream
164- . take_while ( |r| matches ! ( r, ( Ok ( Some ( _) ) , _) | ( Err ( _) , _) ) )
165- . map ( |r| match r {
166- ( Ok ( Some ( cmd) ) , response_tx) => Ok ( ( cmd, response_tx) ) ,
167- ( Err ( e) , response_tx) => {
168- let fatal_error = FatalRequestError (
169- anyhow:: Error :: new ( e) . context ( "Error decoding command" ) ,
170- response_tx,
171- ) ;
172- Err ( fatal_error)
173- }
174- ( Ok ( None ) , _) => unreachable ! ( ) ,
175- } ) ;
176- Box :: pin ( cmd_stream)
177- }
178151}
179152
180- /// Indicates a clean client shutdown - the client properly closed its connection
181- /// after completing all pending writes. This is NOT an error condition.
153+ /// Indicates a clean client shutdown - happened after good bye.
182154#[ derive( Debug , Error ) ]
183155#[ error( "Client closed connection cleanly" ) ]
184156struct CleanShutdown ;
185157
186158/// Indicates the client disconnected unexpectedly (no client_shutdown received,
187- /// or client_shutdown reported an unclean exit). Reported as a connectivity
188- /// issue, not a protocol error.
159+ /// or client_shutdown reported an unclean exit).
189160#[ derive( Debug , Error ) ]
190161#[ error( "client disconnected forcefully: {0}" ) ]
191162struct ForcefulDisconnect ( String ) ;
192163
193- /// A fatal error occurred while processing a request. The client will be sent
194- /// a FatalError response and the connection will be closed .
164+ /// A fatal error occurred while processing a request. The extension will be
165+ /// sent a FatalError response and the client will be abandoned .
195166#[ derive( Error ) ]
196167#[ error( "{0}" ) ]
197168struct FatalRequestError ( anyhow:: Error , oneshot:: Sender < sidecar_msg:: HelperResponse > ) ;
@@ -203,15 +174,16 @@ impl fmt::Debug for FatalRequestError {
203174}
204175
205176async fn do_client_entrypoint (
206- client : & mut Client ,
177+ mut client : Client ,
178+ rx : mpsc:: Receiver < HelperRequest > ,
207179 cancel_token : CancellationToken ,
208180) -> anyhow:: Result < ( ) > {
209- let mut cmd_stream = client . req_stream ( ) ;
181+ let mut cmd_stream = make_command_stream ( rx ) ;
210182
211183 // first, client_init
212184 match recv_command ( & mut cmd_stream, & cancel_token) . await {
213185 Ok ( ( protocol:: Command :: ClientInit ( args) , response_tx) ) => {
214- let resp = handle_client_init ( client, * args) ;
186+ let resp = handle_client_init ( & mut client, * args) ;
215187 match resp {
216188 Ok ( resp) => {
217189 send_command_resp ( response_tx, resp) ?;
@@ -229,26 +201,10 @@ async fn do_client_entrypoint(
229201 }
230202 }
231203 }
232- Ok ( ( protocol:: Command :: ClientShutdown ( args) , response_tx) ) => {
233- let res = handle_client_shutdown ( response_tx, * args) ;
234- return if res
235- . as_ref ( )
236- . err ( )
237- . map_or ( false , |e| e. is :: < CleanShutdown > ( ) )
238- {
239- Ok ( ( ) )
240- } else {
241- res
242- } ;
243- }
244204 Ok ( ( cmd, response_tx) ) => {
245205 send_command_resp ( response_tx, CommandResponse :: FatalError ) ?;
246206 anyhow:: bail!( "expected client_init, got {:?}" , cmd) ;
247207 }
248- Err ( e) if e. is :: < CleanShutdown > ( ) => {
249- info ! ( "client session was dropped" ) ;
250- return Ok ( ( ) ) ;
251- }
252208 Err ( e) if e. is :: < FatalRequestError > ( ) => {
253209 let FatalRequestError ( inner_err, response_tx) = e
254210 . downcast :: < FatalRequestError > ( )
@@ -263,7 +219,7 @@ async fn do_client_entrypoint(
263219
264220 // then the request loop
265221 loop {
266- match do_request_loop_iter ( client, & mut cmd_stream, & cancel_token) . await {
222+ match do_request_loop_iter ( & mut client, & mut cmd_stream, & cancel_token) . await {
267223 Ok ( _) => {
268224 debug ! ( "request done; waiting for new one" ) ;
269225 }
@@ -288,6 +244,31 @@ async fn do_client_entrypoint(
288244 }
289245}
290246
247+ fn make_command_stream ( rx : mpsc:: Receiver < HelperRequest > ) -> CommandStream {
248+ let cmd_stream = ReceiverStream :: new ( rx) ;
249+ let cmd_stream = StreamExt :: map ( cmd_stream, |msg| {
250+ let mut codec = protocol:: CommandCodec ;
251+ (
252+ codec. decode_eof ( & mut BytesMut :: from ( msg. command ) ) ,
253+ msg. response_tx ,
254+ )
255+ } ) ;
256+ let cmd_stream = cmd_stream
257+ . take_while ( |r| matches ! ( r, ( Ok ( Some ( _) ) , _) | ( Err ( _) , _) ) )
258+ . map ( |r| match r {
259+ ( Ok ( Some ( cmd) ) , response_tx) => Ok ( ( cmd, response_tx) ) ,
260+ ( Err ( e) , response_tx) => {
261+ let fatal_error = FatalRequestError (
262+ anyhow:: Error :: new ( e) . context ( "Error decoding command" ) ,
263+ response_tx,
264+ ) ;
265+ Err ( fatal_error)
266+ }
267+ ( Ok ( None ) , _) => unreachable ! ( ) ,
268+ } ) ;
269+ Box :: pin ( cmd_stream)
270+ }
271+
291272fn handle_client_init (
292273 client : & mut Client ,
293274 args : protocol:: ClientInitArgs ,
@@ -420,7 +401,8 @@ async fn do_request_loop_iter(
420401 cmd_stream : & mut CommandStream ,
421402 cancel_token : & CancellationToken ,
422403) -> anyhow:: Result < ( ) > {
423- // wait for any number of config_syncs, followed by request_init
404+ // wait for any number of config_syncs, followed by request_init.
405+ // client_shutdown may be gotten at any time
424406 let mut req_ctx = match recv_command ( cmd_stream, cancel_token) . await ? {
425407 ( protocol:: Command :: ClientShutdown ( args) , response_tx) => {
426408 return handle_client_shutdown ( response_tx, * args) ;
@@ -758,7 +740,7 @@ impl ReqContext {
758740 . waf_subctxs
759741 . remove ( subctx_id)
760742 . or_else ( || self . waf_ctx . new_subcontext ( ) . ok ( ) ) // error should not happen
761- . ok_or ( anyhow ! ( "Failed to create subcontext" ) ) ?;
743+ . ok_or_else ( || anyhow ! ( "Failed to create subcontext" ) ) ?;
762744 Ok ( RunnableCtx :: Owned ( subctx) )
763745 } else {
764746 let waf_ctx = & mut self . waf_ctx ;
@@ -873,7 +855,7 @@ fn convert_actions(
873855 let parameters = kv
874856 . value ( )
875857 . as_type :: < libddwaf:: object:: WafMap > ( )
876- . ok_or ( anyhow ! ( "Action parameter map not a map" ) ) ?
858+ . ok_or_else ( || anyhow ! ( "Action parameter map not a map" ) ) ?
877859 . iter ( )
878860 . try_fold ( HashMap :: new ( ) , |mut acc, kv| -> anyhow:: Result < _ > {
879861 let key = kv. key_str ( ) . map_err ( |e| anyhow ! ( e. to_string( ) ) ) ?;
@@ -1335,12 +1317,8 @@ mod tests {
13351317 Box :: leak ( bytes. into_boxed_slice ( ) )
13361318 }
13371319
1338- fn make_test_client ( ) -> ( Client , mpsc:: Sender < HelperRequest > ) {
1339- let service_manager: & ' static ServiceManager =
1340- Box :: leak ( Box :: new ( ServiceManager :: new ( ) ) ) ;
1341- let client = Client :: new ( service_manager) ;
1342- let sender = client. get_req_sender ( ) ;
1343- ( client, sender)
1320+ fn make_channel ( ) -> ( mpsc:: Sender < HelperRequest > , mpsc:: Receiver < HelperRequest > ) {
1321+ mpsc:: channel ( 5 )
13441322 }
13451323
13461324 fn enqueue_messages ( tx : & mpsc:: Sender < HelperRequest > , messages : Vec < Vec < u8 > > ) {
@@ -1354,18 +1332,12 @@ mod tests {
13541332 }
13551333 }
13561334
1357- fn close_request_channel ( client : & mut Client , tx : mpsc:: Sender < HelperRequest > ) {
1358- let ( dummy_tx, _dummy_rx) = mpsc:: channel ( 1 ) ;
1359- client. req_sender = dummy_tx;
1360- drop ( tx) ;
1361- }
1362-
13631335 /// Bare EOF without a prior client_shutdown is treated as a forceful disconnect
13641336 #[ tokio:: test]
13651337 async fn test_eof_without_goodbye_returns_forceful_disconnect ( ) {
1366- let ( mut client , tx ) = make_test_client ( ) ;
1367- close_request_channel ( & mut client , tx) ;
1368- let mut stream = client . req_stream ( ) ;
1338+ let ( tx , rx ) = make_channel ( ) ;
1339+ drop ( tx) ;
1340+ let mut stream = make_command_stream ( rx ) ;
13691341 let cancel_token = CancellationToken :: new ( ) ;
13701342 let err = match recv_command ( & mut stream, & cancel_token) . await {
13711343 Ok ( _) => panic ! ( "expected forceful disconnect error" ) ,
@@ -1381,10 +1353,10 @@ mod tests {
13811353 /// Incomplete data should return a fatal request decode error
13821354 #[ tokio:: test]
13831355 async fn test_incomplete_data_returns_fatal_request_error ( ) {
1384- let ( mut client , tx ) = make_test_client ( ) ;
1356+ let ( tx , rx ) = make_channel ( ) ;
13851357 enqueue_messages ( & tx, vec ! [ b"dds" . to_vec( ) ] ) ;
1386- close_request_channel ( & mut client , tx) ;
1387- let mut stream = client . req_stream ( ) ;
1358+ drop ( tx) ;
1359+ let mut stream = make_command_stream ( rx ) ;
13881360 let cancel_token = CancellationToken :: new ( ) ;
13891361 let err = match recv_command ( & mut stream, & cancel_token) . await {
13901362 Ok ( _) => panic ! ( "expected decode failure" ) ,
@@ -1405,10 +1377,10 @@ mod tests {
14051377 invalid. extend_from_slice ( & 10u32 . to_le_bytes ( ) ) ;
14061378 invalid. extend_from_slice ( b"0123456789" ) ;
14071379
1408- let ( mut client , tx ) = make_test_client ( ) ;
1380+ let ( tx , rx ) = make_channel ( ) ;
14091381 enqueue_messages ( & tx, vec ! [ invalid] ) ;
1410- close_request_channel ( & mut client , tx) ;
1411- let mut stream = client . req_stream ( ) ;
1382+ drop ( tx) ;
1383+ let mut stream = make_command_stream ( rx ) ;
14121384 let cancel_token = CancellationToken :: new ( ) ;
14131385 let err = match recv_command ( & mut stream, & cancel_token) . await {
14141386 Ok ( _) => panic ! ( "expected invalid header error" ) ,
@@ -1421,56 +1393,16 @@ mod tests {
14211393 ) ;
14221394 }
14231395
1424- /// client_shutdown with C wire format (num_args=1 wraps the map in a 1-element array)
1425- /// decodes correctly. Without this test the protocol mismatch is silent.
1426- #[ tokio:: test]
1427- async fn test_client_shutdown_c_wire_format_decodes ( ) {
1428- use rmp_serde:: Serializer ;
1429- use serde:: Serialize ;
1430- // C side produces: ("client_shutdown", ({clean: true, error: nil},))
1431- // The outer tuple's second element is a 1-element tuple (array) wrapping the map.
1432- #[ derive( serde:: Serialize ) ]
1433- struct Inner {
1434- clean : bool ,
1435- error : Option < String > ,
1436- }
1437- let msg = (
1438- "client_shutdown" ,
1439- ( Inner {
1440- clean : true ,
1441- error : None ,
1442- } , ) ,
1443- ) ;
1444- let mut body = Vec :: new ( ) ;
1445- msg. serialize ( & mut Serializer :: new ( & mut body) ) . unwrap ( ) ;
1446- let framed = serialize_message_raw ( body) ;
1447-
1448- let ( mut client, tx) = make_test_client ( ) ;
1449- enqueue_messages ( & tx, vec ! [ framed] ) ;
1450- drop ( tx) ;
1451- let mut stream = client. req_stream ( ) ;
1452- let cancel_token = CancellationToken :: new ( ) ;
1453- let ( cmd, _response_tx) = recv_command ( & mut stream, & cancel_token)
1454- . await
1455- . expect ( "should decode client_shutdown in C wire format" ) ;
1456- assert ! (
1457- matches!( cmd, protocol:: Command :: ClientShutdown ( _) ) ,
1458- "should decode as ClientShutdown, got {:?}" ,
1459- cmd
1460- ) ;
1461- }
1462-
14631396 /// Cancellation should return CleanShutdown (treated same as clean close)
14641397 #[ tokio:: test]
14651398 async fn test_cancellation_returns_clean_shutdown ( ) {
1466- let ( mut client , tx ) = make_test_client ( ) ;
1467- let mut stream = client . req_stream ( ) ;
1399+ let ( _tx , rx ) = make_channel ( ) ;
1400+ let mut stream = make_command_stream ( rx ) ;
14681401 let cancel_token = CancellationToken :: new ( ) ;
14691402
14701403 cancel_token. cancel ( ) ;
14711404
1472- drop ( tx) ;
1473-
1405+ // _tx stays alive so only the cancel branch is immediately ready in select!
14741406 let err = match recv_command ( & mut stream, & cancel_token) . await {
14751407 Ok ( _) => panic ! ( "expected cancellation as clean shutdown" ) ,
14761408 Err ( err) => err,
0 commit comments