Skip to content

Commit 0c2e10a

Browse files
authored
Merge pull request #58 from unisoncomputing/diff-api-tweaks
diff api tweaks
2 parents 3b6d86f + c5c79f8 commit 0c2e10a

29 files changed

Lines changed: 967 additions & 649 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-- Delete all previously-computed namespace diffs, because the diff payload is different now (we explicitly store
2+
-- errors).
3+
TRUNCATE namespace_diffs;

src/Share/Backend.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ displayType = \case
231231
pure (UserObject decl)
232232

233233
evalDocRef ::
234-
Codebase.CodebaseRuntime ->
234+
Codebase.CodebaseRuntime IO ->
235235
V2.TermReference ->
236236
Codebase.CodebaseM e (Doc.EvaluatedDoc Symbol)
237237
evalDocRef (CodebaseRuntime {codeLookup, cachedEvalResult, unisonRuntime}) termRef = do
@@ -245,6 +245,7 @@ evalDocRef (CodebaseRuntime {codeLookup, cachedEvalResult, unisonRuntime}) termR
245245

246246
typeOf :: Referent.Referent -> Codebase.CodebaseM e (Maybe (V1.Type Symbol ()))
247247
typeOf termRef = fmap void <$> Codebase.loadTypeOfReferent (Cv.referent1to2 termRef)
248+
248249
eval :: V1.Term Symbol a -> Codebase.CodebaseM e (Maybe (V1.Term Symbol ()))
249250
eval (Term.amap (const mempty) -> tm) = do
250251
-- We use an empty ppe for evalutation, it's only used for adding additional context to errors.

src/Share/BackgroundJobs/Diffs/ContributionDiffs.hs

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import Share.BackgroundJobs.Diffs.Queries qualified as DQ
77
import Share.BackgroundJobs.Errors (reportError)
88
import Share.BackgroundJobs.Monad (Background, withTag)
99
import Share.BackgroundJobs.Workers (newWorker)
10-
import Share.Branch (Branch (..))
10+
import Share.Branch (branchCausals_)
1111
import Share.Codebase qualified as Codebase
1212
import Share.Contribution (Contribution (..))
13+
import Share.Env qualified as Env
1314
import Share.IDs
1415
import Share.IDs qualified as IDs
1516
import Share.Metrics qualified as Metrics
16-
import Share.NamespaceDiffs (NamespaceDiffError (MissingEntityError))
1717
import Share.Postgres qualified as PG
1818
import Share.Postgres.Contributions.Queries qualified as ContributionsQ
1919
import Share.Postgres.Notifications qualified as Notif
@@ -23,6 +23,7 @@ import Share.Utils.Logging qualified as Logging
2323
import Share.Web.Authorization qualified as AuthZ
2424
import Share.Web.Errors (EntityMissing (..), ErrorID (..))
2525
import Share.Web.Share.Diffs.Impl qualified as Diffs
26+
import System.Clock qualified as Clock
2627

2728
-- | Check every 10 minutes if we haven't heard on the notifications channel.
2829
-- Just in case we missed a notification.
@@ -32,43 +33,70 @@ maxPollingIntervalSeconds = 10 * 60
3233
worker :: Ki.Scope -> Background ()
3334
worker scope = do
3435
authZReceipt <- AuthZ.backgroundJobAuthZ
36+
badUnliftCodebaseRuntime <- Codebase.badAskUnliftCodebaseRuntime
37+
unisonRuntime <- asks Env.sandboxedRuntime
38+
let makeRuntime :: Codebase.CodebaseEnv -> IO (Codebase.CodebaseRuntime IO)
39+
makeRuntime codebase = do
40+
runtime <- Codebase.codebaseRuntimeTransaction unisonRuntime codebase
41+
pure (badUnliftCodebaseRuntime runtime)
3542
newWorker scope "diffs:contributions" $ forever do
3643
Notif.waitOnChannel Notif.ContributionDiffChannel (maxPollingIntervalSeconds * 1000000)
37-
processDiffs authZReceipt >>= \case
38-
Left (contributionId, e) ->
39-
withTag "contribution-id" (IDs.toText contributionId) $ do
40-
reportError e
41-
Right _ -> pure ()
44+
processDiffs authZReceipt makeRuntime
4245

43-
processDiffs :: AuthZ.AuthZReceipt -> Background (Either (ContributionId, NamespaceDiffError) ())
44-
processDiffs authZReceipt = Metrics.recordContributionDiffDuration . runExceptT $ do
45-
mayContributionId <- PG.runTransaction DQ.claimContributionToDiff
46-
for_ mayContributionId (diffContribution authZReceipt)
47-
case mayContributionId of
48-
Just contributionId -> do
49-
Logging.textLog ("Recomputed contribution diff: " <> tShow contributionId)
50-
& Logging.withTag ("contribution-id", tShow contributionId)
51-
& Logging.withSeverity Logging.Info
52-
& Logging.logMsg
53-
-- Keep processing releases until we run out of them.
54-
either throwError pure =<< lift (processDiffs authZReceipt)
55-
Nothing -> pure ()
46+
-- Process diffs until we run out of them. We claim a diff in a transaction and compute the diff in the same
47+
-- transaction, with a row lock on the contribution id (which is skipped by other workers). There's therefore no chance
48+
-- that we claim a diff but fail to write the result of computing that diff back to the database.
49+
processDiffs :: AuthZ.AuthZReceipt -> (Codebase.CodebaseEnv -> IO (Codebase.CodebaseRuntime IO)) -> Background ()
50+
processDiffs authZReceipt makeRuntime = do
51+
let loop :: Background ()
52+
loop = do
53+
result <-
54+
PG.runTransactionMode PG.RepeatableRead PG.ReadWrite do
55+
DQ.claimContributionToDiff >>= \case
56+
Nothing -> pure Nothing
57+
Just contributionId -> do
58+
startTime <- PG.transactionUnsafeIO (Clock.getTime Clock.Monotonic)
59+
result <- PG.catchTransaction (maybeComputeAndStoreCausalDiff authZReceipt makeRuntime contributionId)
60+
pure (Just (contributionId, startTime, result))
61+
whenJust result \(contributionId, startTime, result) -> do
62+
withTag "contribution-id" (IDs.toText contributionId) do
63+
case result of
64+
Left err -> reportError err
65+
Right didWork -> do
66+
when didWork do
67+
liftIO (Metrics.recordContributionDiffDuration startTime)
68+
Logging.textLog "Computed contribution diff"
69+
& Logging.withSeverity Logging.Info
70+
& Logging.logMsg
71+
loop
72+
loop
5673

57-
diffContribution :: AuthZ.AuthZReceipt -> ContributionId -> ExceptT (ContributionId, NamespaceDiffError) Background ()
58-
diffContribution authZReceipt contributionId = withExceptT (contributionId,) . mapExceptT (withTag "contribution-id" (IDs.toText contributionId)) $ do
59-
( bestCommonAncestorCausalId,
60-
project,
61-
newBranch@Branch {causal = newBranchCausalId},
62-
oldBranch@Branch {causal = oldBranchCausalId}
63-
) <- ExceptT $ PG.tryRunTransaction $ do
64-
Contribution {bestCommonAncestorCausalId, sourceBranchId = newBranchId, targetBranchId = oldBranchId, projectId} <- ContributionsQ.contributionById contributionId
65-
project <- Q.projectById projectId `whenNothingM` throwError (MissingEntityError $ EntityMissing (ErrorID "project:missing") "Project not found")
66-
newBranch <- Q.branchById newBranchId `whenNothingM` throwError (MissingEntityError $ EntityMissing (ErrorID "branch:missing") "Source branch not found")
67-
oldBranch <- Q.branchById oldBranchId `whenNothingM` throwError (MissingEntityError $ EntityMissing (ErrorID "branch:missing") "Target branch not found")
68-
pure (bestCommonAncestorCausalId, project, newBranch, oldBranch)
74+
-- Check whether a causal diff has already been computed, and if it hasn't, compute and store it. Otherwise, do nothing.
75+
-- Returns whether or not we did any work.
76+
maybeComputeAndStoreCausalDiff ::
77+
AuthZ.AuthZReceipt ->
78+
(Codebase.CodebaseEnv -> IO (Codebase.CodebaseRuntime IO)) ->
79+
ContributionId ->
80+
PG.Transaction EntityMissing Bool
81+
maybeComputeAndStoreCausalDiff authZReceipt makeRuntime contributionId = do
82+
Contribution {bestCommonAncestorCausalId, sourceBranchId = newBranchId, targetBranchId = oldBranchId, projectId} <-
83+
ContributionsQ.contributionById contributionId
84+
project <- Q.projectById projectId `whenNothingM` throwError (EntityMissing (ErrorID "project:missing") "Project not found")
85+
newBranch <- Q.branchById newBranchId `whenNothingM` throwError (EntityMissing (ErrorID "branch:missing") "Source branch not found")
86+
oldBranch <- Q.branchById oldBranchId `whenNothingM` throwError (EntityMissing (ErrorID "branch:missing") "Target branch not found")
6987
let oldCodebase = Codebase.codebaseForProjectBranch authZReceipt project oldBranch
7088
let newCodebase = Codebase.codebaseForProjectBranch authZReceipt project newBranch
71-
-- This method saves the diff so it'll be there when we need it, so we don't need to do anything with it.
72-
_ <-
73-
Diffs.diffCausals authZReceipt (oldCodebase, oldBranchCausalId) (newCodebase, newBranchCausalId) bestCommonAncestorCausalId
74-
pure ()
89+
let oldCausal = oldBranch ^. branchCausals_
90+
let newCausal = newBranch ^. branchCausals_
91+
ContributionsQ.existsPrecomputedNamespaceDiff (oldCodebase, oldCausal) (newCodebase, newCausal) >>= \case
92+
True -> pure False
93+
False -> do
94+
oldRuntime <- PG.transactionUnsafeIO (makeRuntime oldCodebase)
95+
newRuntime <- PG.transactionUnsafeIO (makeRuntime newCodebase)
96+
_ <-
97+
Diffs.computeAndStoreCausalDiff
98+
authZReceipt
99+
(oldCodebase, oldRuntime, oldCausal)
100+
(newCodebase, newRuntime, newCausal)
101+
bestCommonAncestorCausalId
102+
pure True

src/Share/BackgroundJobs/Diffs/Queries.hs

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,20 @@ module Share.BackgroundJobs.Diffs.Queries
44
)
55
where
66

7-
import Data.Foldable (toList)
8-
import Data.Set (Set)
97
import Share.IDs
108
import Share.Postgres
119
import Share.Postgres.Notifications qualified as Notif
10+
import Unison.Prelude
1211

1312
submitContributionsToBeDiffed :: (QueryM m) => Set ContributionId -> m ()
1413
submitContributionsToBeDiffed contributions = do
1514
execute_
1615
[sql|
17-
WITH new_contributions(contribution_id) AS (
18-
SELECT * FROM ^{singleColumnTable (toList contributions)}
19-
)
20-
INSERT INTO contribution_diff_queue (contribution_id)
21-
SELECT nc.contribution_id FROM new_contributions nc
22-
ON CONFLICT DO NOTHING
16+
WITH new_contributions(contribution_id) AS (
17+
SELECT * FROM ^{singleColumnTable (toList contributions)}
18+
)
19+
INSERT INTO contribution_diff_queue (contribution_id)
20+
SELECT nc.contribution_id FROM new_contributions nc
2321
|]
2422
Notif.notifyChannel Notif.ContributionDiffChannel
2523

@@ -28,16 +26,16 @@ claimContributionToDiff :: Transaction e (Maybe ContributionId)
2826
claimContributionToDiff = do
2927
query1Col
3028
[sql|
31-
WITH chosen_contribution(contribution_id) AS (
32-
SELECT q.contribution_id
33-
FROM contribution_diff_queue q
34-
ORDER BY q.created_at ASC
35-
LIMIT 1
36-
-- Skip any that are being synced by other workers.
37-
FOR UPDATE SKIP LOCKED
38-
)
39-
DELETE FROM contribution_diff_queue
40-
USING chosen_contribution
41-
WHERE contribution_diff_queue.contribution_id = chosen_contribution.contribution_id
42-
RETURNING chosen_contribution.contribution_id
29+
WITH chosen_contribution(contribution_id) AS (
30+
SELECT q.contribution_id
31+
FROM contribution_diff_queue q
32+
ORDER BY q.created_at ASC
33+
LIMIT 1
34+
-- Skip any that are being synced by other workers.
35+
FOR UPDATE SKIP LOCKED
36+
)
37+
DELETE FROM contribution_diff_queue
38+
USING chosen_contribution
39+
WHERE contribution_diff_queue.contribution_id = chosen_contribution.contribution_id
40+
RETURNING chosen_contribution.contribution_id
4341
|]

src/Share/Branch.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ instance (Hasql.DecodeValue causal) => Hasql.DecodeRow (Branch causal) where
5555
creatorId <- PG.decodeField
5656
pure $ Branch {..}
5757

58-
branchCausals_ :: Traversal (Branch causal) (Branch causal') causal causal'
58+
branchCausals_ :: Lens (Branch causal) (Branch causal') causal causal'
5959
branchCausals_ f Branch {..} = (\causal -> Branch {causal, ..}) <$> f causal
6060

6161
branchCodebaseUser :: Branch causal -> UserId

0 commit comments

Comments
 (0)