Skip to content

Commit d5ba4a8

Browse files
Simon Marlowfacebook-github-bot
authored andcommitted
Add a little safety to the write lock API
Summary: Ensure that we have the write lock when calling storage operations that need it. This isn't foolproof but it helps make things more explicit. Reviewed By: donsbot Differential Revision: D69057011 fbshipit-source-id: 0db9eb65f372438d27a4ecf3af8c5ce7883e5d28
1 parent 81b0a80 commit d5ba4a8

File tree

9 files changed

+55
-44
lines changed

9 files changed

+55
-44
lines changed

glean/db/Glean/Database/CompletePredicates.hs

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,12 @@ syncCompletePredicates env repo =
7070
withOpenDatabase env repo $ \OpenDB{..} -> do
7171
own <- Storage.computeOwnership odbHandle base
7272
(schemaInventory odbSchema)
73-
withWriteLock odbWriting $ Storage.storeOwnership odbHandle own
73+
withWriteLock odbWriting $ \lock ->
74+
Storage.storeOwnership odbHandle lock own
7475
maybeOwnership <- readTVarIO odbOwnership
7576
forM_ maybeOwnership $ \ownership -> do
7677
stats <- getOwnershipStats ownership
7778
logInfo $ "ownership propagation complete: " <> showOwnershipStats stats
78-
where
79-
withWriteLock Nothing f = f
80-
-- if there's no write lock, we must be in the finalization
81-
-- phase and the DB has already been marked read-only. We have
82-
-- exclusive write access at this point so it's safe to
83-
-- continue.
84-
withWriteLock (Just writing) f =
85-
withMutex (wrLock writing) $ const f
8679

8780
completeAxiomPredicates :: Env -> Repo -> IO CompletePredicatesResponse
8881
completeAxiomPredicates env@Env{..} repo = do
@@ -122,17 +115,22 @@ syncCompleteDerivedPredicate env repo pid =
122115
maybeBase <- repoParent env repo
123116
let withBase repo f = readDatabase env repo $ \_ lookup -> f (Just lookup)
124117
maybe ($ Nothing) withBase maybeBase $ \base ->
125-
withWriteLock odbWriting $ do
126-
computed <- Storage.computeDerivedOwnership odbHandle ownership base pid
127-
Storage.storeOwnership odbHandle computed
128-
where
129-
withWriteLock Nothing f = f
130-
-- if there's no write lock, we must be in the finalization
131-
-- phase and the DB has already been marked read-only. We have
132-
-- exclusive write access at this point so it's safe to
133-
-- continue.
134-
withWriteLock (Just writing) f =
135-
withMutex (wrLock writing) $ const f
118+
withWriteLock odbWriting $ \lock -> do
119+
computed <- Storage.computeDerivedOwnership
120+
odbHandle lock ownership base pid
121+
Storage.storeOwnership odbHandle lock computed
122+
123+
withWriteLock
124+
:: Maybe Writing
125+
-> (forall w . Storage.WriteLock w -> IO b)
126+
-> IO b
127+
withWriteLock Nothing f = f (undefined :: Storage.WriteLock ())
128+
-- if there's no write lock, we must be in the finalization
129+
-- phase and the DB has already been marked read-only. We have
130+
-- exclusive write access at this point so it's safe to
131+
-- continue.
132+
withWriteLock (Just writing) f =
133+
withMutexSafe (wrLock writing) f
136134

137135
-- | Kick off completion of externally derived predicate asynchronously
138136
completeDerivedPredicate

glean/db/Glean/Database/Open.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ setupWriting Env{..} lookup = do
447447
(fromIntegral $ ServerConfig.config_db_writer_threads scfg)
448448
envLookupCacheStats
449449
next_id <- newIORef =<< Lookup.firstFreeId lookup
450-
mutex <- newMutex ()
450+
mutex <- newMutex (Storage.WriteLock ())
451451
queue <- WriteQueue <$> newTQueueIO <*> newTVarIO 0 <*> newTVarIO 0
452452
<*> newTVarIO 0 <*> newTVarIO 0
453453
anchorName <- newTVarIO Nothing

glean/db/Glean/Database/Storage.hs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module Glean.Database.Storage
1212
, Storage(..)
1313
, DBVersion(..)
1414
, AxiomOwnership
15+
, WriteLock(..)
1516
, canOpenVersion
1617
, currentVersion
1718
, writableVersions
@@ -72,6 +73,9 @@ data Mode
7273
-- from unit name to ranges of fact IDs.
7374
type AxiomOwnership = HashMap ByteString (VS.Vector Fid)
7475

76+
-- | Token representing the write lock
77+
data WriteLock w = WriteLock w
78+
7579
-- | An abstract storage for fact database
7680
class CanLookup (Database s) => Storage s where
7781
-- | A fact database
@@ -113,7 +117,7 @@ class CanLookup (Database s) => Storage s where
113117
commit :: Database s -> FactSet -> IO ()
114118

115119
-- | Add ownership data about a set of (committed) facts.
116-
addOwnership :: Database s -> AxiomOwnership -> IO ()
120+
addOwnership :: Database s -> WriteLock w -> AxiomOwnership -> IO ()
117121

118122
-- | Optimise a database for reading. This is typically done before backup.
119123
optimize :: Database s -> Bool {- compact -} -> IO ()
@@ -126,7 +130,7 @@ class CanLookup (Database s) => Storage s where
126130
-> Inventory
127131
-> IO ComputedOwnership
128132

129-
storeOwnership :: Database s -> ComputedOwnership -> IO ()
133+
storeOwnership :: Database s -> WriteLock w -> ComputedOwnership -> IO ()
130134

131135
-- | Fetch the 'Ownership' interface for this DB. This is used to
132136
-- make a 'Slice' (a view of a subset of the facts in the DB).
@@ -140,11 +144,12 @@ class CanLookup (Database s) => Storage s where
140144
getUnit :: Database s -> UnitId -> IO (Maybe ByteString)
141145

142146
-- | Called once per batch.
143-
addDefineOwnership :: Database s -> DefineOwnership -> IO ()
147+
addDefineOwnership :: Database s -> WriteLock w -> DefineOwnership -> IO ()
144148

145149
-- | Called once per derived predicate at the end of its derivation.
146150
computeDerivedOwnership
147151
:: Database s
152+
-> WriteLock w
148153
-> Ownership
149154
-> Maybe Lookup
150155
-- ^ Base DB lookup if this is a stacked DB, because we may

glean/db/Glean/Database/Storage/Memory.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,15 @@ instance Storage Memory where
7979
commit db facts = FactSet.append (dbFacts db) facts
8080

8181
-- TODO: ownership
82-
addOwnership _ _ = return ()
82+
addOwnership _ _ _ = return ()
8383

8484
optimize _ _ = return ()
8585

8686
-- TODO: ownership
8787
computeOwnership _ _ _ = return (error "unimplemented computeOwnership")
8888
getUnitId _ _ = return (error "unimplemented getUnitId")
8989
getUnit _ _ = return (error "unimplemented getUnit")
90-
storeOwnership _ _ = return () -- can't fail, otherwise we fail tests
90+
storeOwnership _ _ _ = return () -- can't fail, otherwise we fail tests
9191
getOwnership _ = return Nothing
9292
addDefineOwnership _ _ =
9393
return (error "unimplemented addDefineOwnership")

glean/db/Glean/Database/Storage/RocksDB.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ instance Storage RocksDB where
155155
commit db facts = unsafeWithForeignPtr (dbPtr db) $ \db_ptr -> do
156156
with facts $ \facts_ptr -> invoke $ glean_rocksdb_commit db_ptr facts_ptr
157157

158-
addOwnership db owned =
158+
addOwnership db _ owned =
159159
unsafeWithForeignPtr (dbPtr db) $ \db_ptr ->
160160
when (not $ HashMap.null owned) $
161161
withMany entry (HashMap.toList owned) $ \xs ->
@@ -187,7 +187,7 @@ instance Storage RocksDB where
187187
using (invoke $ glean_rocksdb_get_ownership_unit_iterator db_ptr) $
188188
Ownership.compute inv db base
189189

190-
storeOwnership db own =
190+
storeOwnership db _ own =
191191
unsafeWithForeignPtr (dbPtr db) $ \db_ptr ->
192192
with own $ \own_ptr ->
193193
invoke $ glean_rocksdb_store_ownership db_ptr own_ptr
@@ -211,12 +211,12 @@ instance Storage RocksDB where
211211
then Just <$> unsafeMallocedByteString unit_ptr unit_size
212212
else return Nothing
213213

214-
addDefineOwnership db define =
214+
addDefineOwnership db _ define =
215215
unsafeWithForeignPtr (dbPtr db) $ \db_ptr ->
216216
with define $ \define_ptr ->
217217
invoke $ glean_rocksdb_add_define_ownership db_ptr define_ptr
218218

219-
computeDerivedOwnership db ownership base (Pid pid) =
219+
computeDerivedOwnership db _ ownership base (Pid pid) =
220220
unsafeWithForeignPtr (dbPtr db) $ \db_ptr ->
221221
using
222222
(invoke $

glean/db/Glean/Database/Types.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import Glean.Database.Catalog (Catalog)
4141
import Glean.Database.Config
4242
import Glean.Database.Meta
4343
import Glean.Database.Schema.Types
44-
import Glean.Database.Storage (Database, Storage, describe)
44+
import Glean.Database.Storage (Database, Storage, describe, WriteLock(..))
4545
import Glean.Database.Trace
4646
import Glean.Database.Work.Heartbeat (Heartbeats)
4747
import Glean.Database.Work.Queue (WorkQueue)
@@ -69,7 +69,7 @@ import Glean.Write.Stats (Stats)
6969
-- Write caches
7070
data Writing = Writing
7171
{ -- Write lock
72-
wrLock :: Mutex ()
72+
wrLock :: Mutex (WriteLock ())
7373

7474
-- First free Id in the write pipeline
7575
, wrNextId :: IORef Fid

glean/db/Glean/Database/Write/Batch.hs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ writeDatabase env repo WriteContent{..} latency =
134134
--
135135
-- TODO: What if someone is already deduplicating another batch? Should we
136136
-- not write in that case?
137-
r <- tryWithMutex (wrLock writing) $ const $
137+
r <- tryWithMutexSafe (wrLock writing) $ \lock ->
138138
reallyWriteBatch
139-
env repo odb lookup writing size False writeBatch writeOwnership
139+
env repo odb lock lookup writing size False writeBatch writeOwnership
140140
case r of
141141
Just cont -> cont
142142
Nothing ->
@@ -148,14 +148,15 @@ reallyWriteBatch
148148
=> Env
149149
-> Repo
150150
-> OpenDB s
151+
-> Storage.WriteLock w
151152
-> Lookup
152153
-> Writing
153154
-> Word64 -- ^ original size of the batch
154155
-> Bool -- ^ has the batch already been de-duped?
155156
-> Thrift.Batch
156157
-> Maybe DefineOwnership
157158
-> IO (IO Subst)
158-
reallyWriteBatch env repo OpenDB{..} lookup writing original_size deduped
159+
reallyWriteBatch env repo OpenDB{..} lock lookup writing original_size deduped
159160
batch@Thrift.Batch{..} maybeOwn = do
160161
let !real_size = batchSize batch
161162
Stats.tick (envStats env) Stats.mutatorThroughput original_size
@@ -191,7 +192,7 @@ reallyWriteBatch env repo OpenDB{..} lookup writing original_size deduped
191192
commitOwnership = do
192193
owned <- mapM (coerce Subst.unsafeSubstIntervalsAndRelease subst)
193194
batch_owned
194-
Storage.addOwnership odbHandle owned
195+
Storage.addOwnership odbHandle lock owned
195196
deps <- mapM (substDependencies subst) batch_dependencies
196197
derivedOwners <-
197198
if | Just owners <- maybeOwn -> do
@@ -201,7 +202,7 @@ reallyWriteBatch env repo OpenDB{..} lookup writing original_size deduped
201202
makeDefineOwnership env repo next_id deps
202203
| otherwise -> return Nothing
203204
forM_ derivedOwners $ \ownBatch ->
204-
Storage.addDefineOwnership odbHandle ownBatch
205+
Storage.addDefineOwnership odbHandle lock ownBatch
205206

206207
doCommit =
207208
tick env repo WriteTraceCommit
@@ -228,7 +229,7 @@ reallyWriteBatch env repo OpenDB{..} lookup writing original_size deduped
228229
doCommit
229230
`finally`
230231
do atomically $ writeTVar (wrCommit writing) Nothing
231-
withMutex (wrLock writing) $ const $ release facts
232+
withMutexSafe (wrLock writing) $ const $ release facts
232233

233234
new_next_id <- Lookup.firstFreeId facts
234235
atomicWriteIORef (wrNextId writing) new_next_id
@@ -290,8 +291,8 @@ deDupBatch env repo odb lookup writing original_size
290291
forM_ maybeOwn $ \ownBatch ->
291292
Ownership.substDefineOwnership ownBatch dsubst
292293
-- And now write it do the DB, deduplicating again
293-
cont <- withMutex (wrLock writing) $ const $
294-
reallyWriteBatch env repo odb lookup writing original_size True
294+
cont <- withMutexSafe (wrLock writing) $ \lock ->
295+
reallyWriteBatch env repo odb lock lookup writing original_size True
295296
deduped_batch
296297
{ Thrift.batch_owned = is
297298
, Thrift.batch_dependencies = deps

glean/db/Glean/Query/Derive.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -546,10 +546,10 @@ finishDerivation env log repo ref pred = do
546546
let withBase repo f =
547547
readDatabase env repo $ \_ lookup -> f (Just lookup)
548548
maybe ($ Nothing) withBase maybeBase $ \base -> do
549-
withMutex (wrLock writing) $ \_ -> do
549+
withMutexSafe (wrLock writing) $ \lock -> do
550550
computed <- Storage.computeDerivedOwnership odbHandle
551-
ownership base (predicatePid details)
552-
Storage.storeOwnership odbHandle computed
551+
lock ownership base (predicatePid details)
552+
Storage.storeOwnership odbHandle lock computed
553553

554554
getDerivation :: Database.Env -> Repo -> PredicateId -> STM Derivation
555555
getDerivation env repo pred = do

glean/util/Glean/Util/Mutex.hs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
-}
88

99
module Glean.Util.Mutex (
10-
Mutex, newMutex, withMutex, withMutex_, tryWithMutex
10+
Mutex, newMutex, withMutex, withMutexSafe, withMutex_,
11+
tryWithMutex, tryWithMutexSafe,
1112
) where
1213

1314
import Control.Concurrent.MVar
@@ -21,6 +22,9 @@ newMutex x = Mutex <$> newMVar x
2122
withMutex :: Mutex a -> (a -> IO b) -> IO b
2223
withMutex (Mutex v) = withMVar v
2324

25+
withMutexSafe :: Mutex (f a) -> (forall s . f s -> IO b) -> IO b
26+
withMutexSafe (Mutex v) = withMVar v
27+
2428
withMutex_ :: Mutex a -> IO b -> IO b
2529
withMutex_ m = withMutex m . const
2630

@@ -30,3 +34,6 @@ tryWithMutex (Mutex v) f =
3034
(tryTakeMVar v)
3135
(maybe (return ()) $ putMVar v)
3236
(traverse f)
37+
38+
tryWithMutexSafe :: Mutex (f a) -> (forall s . f s -> IO b) -> IO (Maybe b)
39+
tryWithMutexSafe = tryWithMutex

0 commit comments

Comments
 (0)