chiark / gitweb /
fix lock order
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 13 Nov 2020 21:15:58 +0000 (21:15 +0000)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 13 Nov 2020 21:15:58 +0000 (21:15 +0000)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
src/accounts.rs
src/bin/daemon-otter.rs
src/cmdlistener.rs
src/global.rs
src/imports.rs

index 71fd276b0b0d8e5068fbaad124ea9ed53af9b0d9..b0239a1060a4175924f0370d8ccf0c6ee3a0e464 100644 (file)
@@ -309,6 +309,7 @@ impl AccountsGuard {
      F: FnOnce(&mut AccountRecord, AccountId) -> T
      >(
       &mut self,
+      games: &mut GamesGuard,
       key: K,
       auth: Authorisation<AccountName>,
       set_access: Option<AccessRecord>,
@@ -320,6 +321,7 @@ impl AccountsGuard {
 
     if let Some(new_access) = set_access {
       process_all_players_for_account(
+        games,
         acctid,
         |ig, player| ig.invalidate_tokens(player)
       )?;
@@ -360,6 +362,7 @@ impl AccountsGuard {
 
   #[throws(MgmtError)]
   pub fn remove_entry(&mut self,
+                      games: &mut GamesGuard,
                       account: &AccountName,
                       _auth: Authorisation<AccountName>)
   {
@@ -369,7 +372,7 @@ impl AccountsGuard {
       then { (accounts, acctid) }
       else { throw!(AccountNotFound) }
     };
-    process_all_players_for_account(acctid, |ig,player| {
+    process_all_players_for_account(games, acctid, |ig,player| {
       ig.players_remove(&[player].iter().cloned().collect())?;
       Ok::<_,ME>(())
     })?;
index 5b197c1f2964c1211c115ee105f9163a6e5be30a..490ba9d36b4648c91bfd1552bcdf6a42793ff286 100644 (file)
@@ -102,6 +102,9 @@ fn resource<'r>(leaf : CheckedResourceLeaf) -> impl Responder<'r> {
 
 #[throws(StartupError)]
 fn main() {
+  // xxx test suite for cli at least
+  // todo test suite for web api
+
   let config_filename = env::args().nth(1);
   ServerConfig::read(config_filename.as_ref().map(String::as_str))?;
 
@@ -119,7 +122,7 @@ fn main() {
   shapelib::load()?;
 
   load_accounts()?;
-  load_games(&mut AccountsGuard::lock())?;
+  load_games(&mut AccountsGuard::lock(), &mut games_lock())?;
 
   let cl = CommandListener::new()?;
   cl.spawn()?;
index f73a5a2ecfd4b78ba95ef21d6cb7567c0f19c9ba..c029da909d505b27b079a2b591a35c9075883774 100644 (file)
@@ -108,9 +108,10 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse {
 
     UpdateAccont(AccountDetails { account, nick, timezone, access }) => {
       let mut ag = AccountsGuard::lock();
+      let mut games = games_lock();
       let auth = authorise_for_account(cs, &ag, &account)?;
       let access = cs.accountrecord_from_spec(access)?;
-      ag.with_entry_mut(&account, auth, access, |record, _acctid|{
+      ag.with_entry_mut(&mut games, &account, auth, access, |record, _acctid|{
         fn update_from<T>(spec: Option<T>, record: &mut T) {
           if let Some(new) = spec { *record = new; }
         }
@@ -124,8 +125,9 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse {
 
     DeleteAccount(account) => {
       let mut ag = AccountsGuard::lock();
+      let mut games = games_lock();
       let auth = authorise_for_account(cs, &ag, &account)?;
-      ag.remove_entry(&account, auth)?;
+      ag.remove_entry(&mut games, &account, auth)?;
       Fine
     }
 
@@ -141,6 +143,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse {
 
     CreateGame { game, insns } => {
       let mut ag = AccountsGuard::lock();
+      let mut games = games_lock();
       let auth = authorise_by_account(cs, &ag, &game)?;
 
       let gs = crate::gamestate::GameState {
@@ -153,7 +156,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse {
       };
 
       let acl = default();
-      let gref = Instance::new(game, gs, acl, auth)?;
+      let gref = Instance::new(game, gs, &mut games, acl, auth)?;
       let ig = gref.lock()?;
       let mut ig = Unauthorised::of(ig);
 
@@ -164,7 +167,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse {
           let ig = ig.by(Authorisation::authorise_any());
           let name = ig.name.clone();
           let InstanceGuard { c, .. } = ig;
-          Instance::destroy_game(c, auth)
+          Instance::destroy_game(&mut games, c, auth)
             .unwrap_or_else(|e| warn!(
               "failed to tidy up failecd creation of {:?}: {:?}",
               &name, &e
@@ -199,10 +202,11 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse {
 
     DestroyGame { game } => {
       let mut ag = AccountsGuard::lock();
+      let mut games = games_lock();
       let auth = authorise_by_account(cs, &mut ag, &game)?;
       let gref = Instance::lookup_by_name(&game, auth)?;
       let ig = gref.lock_even_poisoned();
-      Instance::destroy_game(ig, auth)?;
+      Instance::destroy_game(&mut games, ig, auth)?;
       Fine
     }
 
index 0c95e29fedba01247bb0e78c1e46c6946361be7b..b480129c0eb0af368d2bb21e88dae8f55a49ba36 100644 (file)
@@ -188,7 +188,7 @@ pub struct Global {
   // slow global locks:
   save_area_lock : Mutex<Option<File>>,
   // <- accounts::accounts ->
-  games   : RwLock<HashMap<Arc<InstanceName>,InstanceRef>>,
+  games_table: RwLock<GamesTable>,
 
   // per-game lock:
   // <- InstanceContainer ->
@@ -203,6 +203,9 @@ pub struct Global {
   clients : RwLock<TokenTable<ClientId>>,
 }
 
+pub type GamesGuard = RwLockWriteGuard<'static, GamesTable>;
+pub type GamesTable = HashMap<Arc<InstanceName>,InstanceRef>;
+
 #[derive(Debug)]
 pub struct InstanceContainer {
   live : bool,
@@ -285,6 +288,7 @@ impl Instance {
   #[allow(clippy::new_ret_no_self)]
   #[throws(MgmtError)]
   pub fn new(name: InstanceName, gs: GameState,
+             games: &mut GamesGuard,
              acl: LoadedAcl<TablePermission>, _: Authorisation<InstanceName>)
              -> InstanceRef {
     let name = Arc::new(name);
@@ -309,9 +313,6 @@ impl Instance {
     let gref = InstanceRef(Arc::new(Mutex::new(cont)));
     let mut ig = gref.lock()?;
 
-    // We hold the GLOBAL.games lock while we save the new game.
-    // That lock is not on the hot path.
-    let mut games = GLOBAL.games.write().unwrap();
     let entry = games.entry(name);
 
     use hash_map::Entry::*;
@@ -335,7 +336,7 @@ impl Instance {
       -> Unauthorised<InstanceRef, InstanceName>
   {
     Unauthorised::of(
-      GLOBAL.games.read().unwrap()
+      GLOBAL.games_table.read().unwrap()
         .get(name)
         .ok_or(MgmtError::GameNotFound)?
         .clone()
@@ -350,18 +351,17 @@ impl Instance {
   }
 
   #[throws(InternalError)]
-  pub fn destroy_game(mut g: MutexGuard<InstanceContainer>,
+  pub fn destroy_game(games: &mut GamesGuard,
+                      mut g: MutexGuard<InstanceContainer>,
                       _: Authorisation<InstanceName>) {
     let a_savefile = savefilename(&g.g.name, "a-", "");
 
-    let mut gw = GLOBAL.games.write().unwrap();
-    // xxx lock order wrong, this function should unlock and then relock
     let g_file = savefilename(&g.g.name, "g-", "");
     fs::remove_file(&g_file).context("remove").context(g_file)?;
 
     (||{ // Infallible:
       g.live = false;
-      gw.remove(&g.g.name);
+      games.remove(&g.g.name);
       InstanceGuard::forget_all_tokens(&mut g.g.tokens_clients);
       InstanceGuard::forget_all_tokens(&mut g.g.tokens_players);
     })(); // <- No ?, ensures that IEFE is infallible (barring panics)
@@ -380,7 +380,7 @@ impl Instance {
   pub fn list_names(account: Option<&AccountName>,
                     _: Authorisation<AccountName>)
                     -> Vec<Arc<InstanceName>> {
-    let games = GLOBAL.games.read().unwrap();
+    let games = GLOBAL.games_table.read().unwrap();
     let out : Vec<Arc<InstanceName>> =
       games.keys()
       .filter(|k| account == None || account == Some(&k.account))
@@ -390,6 +390,10 @@ impl Instance {
   }
 }
 
+pub fn games_lock() -> RwLockWriteGuard<'static, GamesTable> {
+  GLOBAL.games_table.write().unwrap()
+}
+
 // ---------- Simple trait implementations ----------
 
 impl Deref for InstanceGuard<'_> {
@@ -898,6 +902,7 @@ impl InstanceGuard<'_> {
 
   #[throws(StartupError)]
   fn load_game(accounts: &AccountsGuard,
+               games: &mut GamesGuard,
                name: InstanceName) -> Option<InstanceRef> {
     {
       let mut st = GLOBAL.save_area_lock.lock().unwrap();
@@ -1007,14 +1012,15 @@ impl InstanceGuard<'_> {
     } }
     drop(global);
     drop(g);
-    GLOBAL.games.write().unwrap().insert(name.clone(), gref.clone());
+    games.insert(name.clone(), gref.clone());
     info!("loadewd {:?}", &name);
     Some(gref)
   }
 }
 
 #[throws(anyhow::Error)]
-pub fn load_games(accounts: &mut AccountsGuard) {
+pub fn load_games(accounts: &mut AccountsGuard,
+                  games: &mut GamesGuard) {
   enum AFState { Found(PathBuf), Used };
   use AFState::*;
   use SavefilenameParseResult::*;
@@ -1037,7 +1043,7 @@ pub fn load_games(accounts: &mut AccountsGuard) {
           );
         },
         GameFile { access_leaf, name } => {
-          InstanceGuard::load_game(accounts, name)?;
+          InstanceGuard::load_game(accounts, games, name)?;
           a_leaves.insert(access_leaf, Used);
         },
       }
@@ -1142,9 +1148,8 @@ pub fn process_all_players_for_account<
     E: Error,
     F: FnMut(&mut InstanceGuard<'_>, PlayerId) -> Result<(),E>
     >
-  (acctid: AccountId, mut f: F)
+  (games: &mut GamesGuard, acctid: AccountId, mut f: F)
 {
-  let games = GLOBAL.games.write().unwrap();
   for gref in games.values() {
     let c = gref.lock_even_poisoned();
     let remove : Vec<_> = c.g.iplayers.iter().filter_map(|(player,pr)| {
@@ -1281,7 +1286,7 @@ fn client_expire_old_clients() {
     fn old(&mut self, client: ClientId) -> Option<Self::Ret>;
   }
 
-  for gref in GLOBAL.games.read().unwrap().values() {
+  for gref in GLOBAL.games_table.read().unwrap().values() {
     struct Any;
     impl ClientIterator for Any {
       type Ret = ();
@@ -1328,7 +1333,7 @@ fn global_expire_old_logs() {
 
   let mut want_expire = vec![];
 
-  let read = GLOBAL.games.read().unwrap();
+  let read = GLOBAL.games_table.read().unwrap();
   for gref in read.values() {
     if gref.lock_even_poisoned().g.gs.want_expire_some_logs(cutoff) {
       want_expire.push(gref.clone())
index 68cdfeac47541d4f505c5ac880f9bc23900ecfca..c0f3c4895bb2cdd810aa9b80624fd3d6982ff43f 100644 (file)
@@ -10,7 +10,8 @@ pub use std::fmt::Formatter;
 pub use std::fmt::{self,Display,Debug};
 pub use std::thread::{self,sleep};
 pub use std::time::Duration;
-pub use std::sync::{Arc,Mutex,MutexGuard,RwLock,RwLockReadGuard,Condvar};
+pub use std::sync::{Arc,Mutex,MutexGuard,Condvar};
+pub use std::sync::{RwLock,RwLockReadGuard,RwLockWriteGuard};
 pub use std::collections::{HashMap,hash_map,HashSet};
 pub use std::hash::Hash;
 pub use std::borrow::Borrow;