@@ -10,7 +10,7 @@ use crate::signatures;
1010use crate :: storage:: Storage ;
1111use crate :: RemoteSettingsContext ;
1212use crate :: { packaged_attachments, packaged_collections, RemoteSettingsServer } ;
13- use parking_lot:: Mutex ;
13+ use parking_lot:: { Mutex , MutexGuard } ;
1414use serde:: { Deserialize , Serialize } ;
1515use sha2:: { Digest , Sha256 } ;
1616use std:: {
@@ -71,6 +71,9 @@ pub struct RemoteSettingsClient<C = ViaductApiClient> {
7171 // This is immutable, so it can be outside the mutex
7272 collection_name : String ,
7373 inner : Mutex < RemoteSettingsClientInner < C > > ,
74+ // Config that we got from `update_config`. This should be applied to
75+ // `RemoteSettingsClientInner` the next time it's used.
76+ pending_config : Mutex < Option < RemoteSettingsClientConfig > > ,
7477}
7578
7679struct RemoteSettingsClientInner < C > {
@@ -79,6 +82,12 @@ struct RemoteSettingsClientInner<C> {
7982 jexl_filter : JexlFilter ,
8083}
8184
85+ struct RemoteSettingsClientConfig {
86+ server_url : BaseUrl ,
87+ bucket_name : String ,
88+ context : Option < RemoteSettingsContext > ,
89+ }
90+
8291// To initially download the dump (and attachments, if any), run:
8392// $ cargo remote-settings dump-get --bucket main --collection-name <collection name>
8493//
@@ -204,7 +213,27 @@ impl<C: ApiClient> RemoteSettingsClient<C> {
204213 api_client,
205214 jexl_filter,
206215 } ) ,
216+ pending_config : Mutex :: new ( None ) ,
217+ }
218+ }
219+
220+ /// Lock the `RemoteSettingsClientInner` field
221+ ///
222+ /// This also applies the pending config if set.
223+ fn lock_inner ( & self ) -> Result < MutexGuard < ' _ , RemoteSettingsClientInner < C > > > {
224+ let pending_config = self . get_pending_config ( ) ;
225+ let mut inner = self . inner . lock ( ) ;
226+ if let Some ( config) = pending_config {
227+ inner. api_client =
228+ C :: create ( config. server_url , config. bucket_name , & self . collection_name ) ;
229+ inner. jexl_filter = JexlFilter :: new ( config. context ) ;
230+ inner. storage . empty ( ) ?;
207231 }
232+ Ok ( inner)
233+ }
234+
235+ fn get_pending_config ( & self ) -> Option < RemoteSettingsClientConfig > {
236+ self . pending_config . lock ( ) . take ( )
208237 }
209238
210239 pub fn collection_name ( & self ) -> & str {
@@ -273,7 +302,7 @@ impl<C: ApiClient> RemoteSettingsClient<C> {
273302 /// If records are not present in storage this will normally return None. Use `sync_if_empty =
274303 /// true` to change this behavior and perform a network request in this case.
275304 pub fn get_records ( & self , sync_if_empty : bool ) -> Result < Option < Vec < RemoteSettingsRecord > > > {
276- let mut inner = self . inner . lock ( ) ;
305+ let mut inner = self . lock_inner ( ) ? ;
277306 let collection_url = inner. api_client . collection_url ( ) ;
278307
279308 // Case 1: The packaged data is more recent than the cache
@@ -322,7 +351,7 @@ impl<C: ApiClient> RemoteSettingsClient<C> {
322351 }
323352
324353 pub fn get_last_modified_timestamp ( & self ) -> Result < Option < u64 > > {
325- let mut inner = self . inner . lock ( ) ;
354+ let mut inner = self . lock_inner ( ) ? ;
326355 let collection_url = inner. api_client . collection_url ( ) ;
327356 inner. storage . get_last_modified_timestamp ( & collection_url)
328357 }
@@ -332,7 +361,7 @@ impl<C: ApiClient> RemoteSettingsClient<C> {
332361 /// 2. Fetches the changeset from the remote server based on the last modified timestamp.
333362 /// 3. Inserts the fetched changeset into local storage.
334363 fn perform_sync_operation ( & self ) -> Result < ( ) > {
335- let mut inner = self . inner . lock ( ) ;
364+ let mut inner = self . lock_inner ( ) ? ;
336365 let collection_url = inner. api_client . collection_url ( ) ;
337366 let timestamp = inner. storage . get_last_modified_timestamp ( & collection_url) ?;
338367 let changeset = inner. api_client . fetch_changeset ( timestamp) ?;
@@ -374,7 +403,7 @@ impl<C: ApiClient> RemoteSettingsClient<C> {
374403
375404 fn reset_storage ( & self ) -> Result < ( ) > {
376405 trace ! ( "{0}: reset local storage." , self . collection_name) ;
377- let mut inner = self . inner . lock ( ) ;
406+ let mut inner = self . lock_inner ( ) ? ;
378407 let collection_url = inner. api_client . collection_url ( ) ;
379408 // Clear existing storage
380409 inner. storage . empty ( ) ?;
@@ -405,7 +434,7 @@ impl<C: ApiClient> RemoteSettingsClient<C> {
405434
406435 #[ cfg( feature = "signatures" ) ]
407436 fn verify_signature ( & self ) -> Result < ( ) > {
408- let mut inner = self . inner . lock ( ) ;
437+ let mut inner = self . lock_inner ( ) ? ;
409438 let collection_url = inner. api_client . collection_url ( ) ;
410439 let timestamp = inner. storage . get_last_modified_timestamp ( & collection_url) ?;
411440 let records = inner. storage . get_records ( & collection_url) ?;
@@ -471,7 +500,7 @@ impl<C: ApiClient> RemoteSettingsClient<C> {
471500 . as_ref ( )
472501 . ok_or_else ( || Error :: RecordAttachmentMismatchError ( "No attachment metadata" . into ( ) ) ) ?;
473502
474- let mut inner = self . inner . lock ( ) ;
503+ let mut inner = self . lock_inner ( ) ? ;
475504 let collection_url = inner. api_client . collection_url ( ) ;
476505
477506 // First try storage - it will only return data that matches our metadata
@@ -521,6 +550,20 @@ impl<C: ApiClient> RemoteSettingsClient<C> {
521550 . set_attachment ( & collection_url, & metadata. location , & attachment) ?;
522551 Ok ( attachment)
523552 }
553+
554+ pub fn update_config (
555+ & self ,
556+ server_url : BaseUrl ,
557+ bucket_name : String ,
558+ context : Option < RemoteSettingsContext > ,
559+ ) {
560+ let mut pending_config = self . pending_config . lock ( ) ;
561+ * pending_config = Some ( RemoteSettingsClientConfig {
562+ server_url,
563+ bucket_name,
564+ context,
565+ } )
566+ }
524567}
525568
526569impl RemoteSettingsClient < ViaductApiClient > {
@@ -536,22 +579,13 @@ impl RemoteSettingsClient<ViaductApiClient> {
536579
537580 Self :: new_from_parts ( collection_name, storage, jexl_filter, api_client)
538581 }
539-
540- pub fn update_config (
541- & self ,
542- server_url : BaseUrl ,
543- bucket_name : String ,
544- context : Option < RemoteSettingsContext > ,
545- ) -> Result < ( ) > {
546- let mut inner = self . inner . lock ( ) ;
547- inner. api_client = ViaductApiClient :: new ( server_url, & bucket_name, & self . collection_name ) ;
548- inner. jexl_filter = JexlFilter :: new ( context) ;
549- inner. storage . empty ( )
550- }
551582}
552583
553584#[ cfg_attr( test, mockall:: automock) ]
554585pub trait ApiClient {
586+ /// Create a new instance of the client
587+ fn create ( server_url : BaseUrl , bucket_name : String , collection_name : & str ) -> Self ;
588+
555589 /// Get the Bucket URL for this client.
556590 ///
557591 /// This is a URL that includes the server URL, bucket name, and collection name. This is used
@@ -609,6 +643,10 @@ impl ViaductApiClient {
609643}
610644
611645impl ApiClient for ViaductApiClient {
646+ fn create ( server_url : BaseUrl , bucket_name : String , collection_name : & str ) -> Self {
647+ Self :: new ( server_url, & bucket_name, collection_name)
648+ }
649+
612650 fn collection_url ( & self ) -> String {
613651 self . endpoints . collection_url . to_string ( )
614652 }
@@ -1882,6 +1920,7 @@ mod test_new_client {
18821920#[ cfg( test) ]
18831921mod jexl_tests {
18841922 use super :: * ;
1923+ use std:: sync:: { Arc , Weak } ;
18851924
18861925 #[ test]
18871926 fn test_get_records_filtered_app_version_pass ( ) {
@@ -2068,6 +2107,51 @@ mod jexl_tests {
20682107 Some ( vec![ ] )
20692108 ) ;
20702109 }
2110+
2111+ // Test that we can't hit the deadlock described in
2112+ // https://bugzilla.mozilla.org/show_bug.cgi?id=2012955
2113+ #[ test]
2114+ fn test_update_config_deadlock ( ) {
2115+ let mut api_client = MockApiClient :: new ( ) ;
2116+ let rs_client_ref: Arc < Mutex < Weak < RemoteSettingsClient < MockApiClient > > > > =
2117+ Arc :: new ( Mutex :: new ( Weak :: new ( ) ) ) ;
2118+ let rs_client_ref2 = rs_client_ref. clone ( ) ;
2119+
2120+ api_client. expect_collection_url ( ) . returning ( move || {
2121+ // While we're in the middle of `get_request` and have the `RemoteSettingsClientInner`
2122+ // locked, call `update_config` to try to trigger the deadlock.
2123+ rs_client_ref2
2124+ . lock ( )
2125+ . upgrade ( )
2126+ . expect ( "rs_client_ref not set" )
2127+ . update_config (
2128+ BaseUrl :: parse ( "https://example.com/" ) . unwrap ( ) ,
2129+ "test-collection" . to_string ( ) ,
2130+ None ,
2131+ ) ;
2132+ "http://rs.example.com/v1/buckets/main/collections/test-collection" . into ( )
2133+ } ) ;
2134+ api_client. expect_is_prod_server ( ) . returning ( || Ok ( false ) ) ;
2135+
2136+ let context = RemoteSettingsContext {
2137+ app_version : Some ( "129.0.0" . to_string ( ) ) ,
2138+ ..Default :: default ( )
2139+ } ;
2140+ let storage = Storage :: new ( ":memory:" . into ( ) ) ;
2141+
2142+ let rs_client = Arc :: new ( RemoteSettingsClient :: new_from_parts (
2143+ "test-collection" . into ( ) ,
2144+ storage,
2145+ JexlFilter :: new ( Some ( context) ) ,
2146+ api_client,
2147+ ) ) ;
2148+ * rs_client_ref. lock ( ) = Arc :: downgrade ( & rs_client) ;
2149+
2150+ assert_eq ! (
2151+ rs_client. get_records( false ) . expect( "Error getting records" ) ,
2152+ None ,
2153+ ) ;
2154+ }
20712155}
20722156
20732157#[ cfg( feature = "signatures" ) ]
0 commit comments