diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridge.php b/src/applications/doorkeeper/bridge/DoorkeeperBridge.php --- a/src/applications/doorkeeper/bridge/DoorkeeperBridge.php +++ b/src/applications/doorkeeper/bridge/DoorkeeperBridge.php @@ -48,4 +48,32 @@ return null; } + final protected function saveExternalObject( + DoorkeeperObjectRef $ref, + DoorkeeperExternalObject $obj) { + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + try { + $obj->save(); + } catch (AphrontDuplicateKeyQueryException $ex) { + + // In various cases, we may race another process importing the same + // data. If we do, we'll collide on the object key. Load the object + // the other process created and use that. + $obj = id(new DoorkeeperExternalObjectQuery()) + ->setViewer($this->getViewer()) + ->withObjectKeys(array($ref->getObjectKey())) + ->executeOne(); + if (!$obj) { + throw new PhutilProxyException( + pht('Failed to load external object after collision.'), + $ex); + } + + $ref->attachExternalObject($obj); + } + unset($unguarded); + } + + } diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php b/src/applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php --- a/src/applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php +++ b/src/applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php @@ -113,10 +113,7 @@ } $this->fillObjectFromData($obj, $result); - - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $obj->save(); - unset($unguarded); + $this->saveExternalObject($ref, $obj); } } diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php --- a/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php +++ b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php @@ -78,10 +78,7 @@ $obj = $ref->getExternalObject(); $this->fillObjectFromData($obj, $result); - - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $obj->save(); - unset($unguarded); + $this->saveExternalObject($ref, $obj); } } diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubUser.php b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubUser.php --- a/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubUser.php +++ b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubUser.php @@ -101,10 +101,7 @@ $obj = $ref->getExternalObject(); $this->fillObjectFromData($obj, $spec); - - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $obj->save(); - unset($unguarded); + $this->saveExternalObject($ref, $obj); } } diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridgeJIRA.php b/src/applications/doorkeeper/bridge/DoorkeeperBridgeJIRA.php --- a/src/applications/doorkeeper/bridge/DoorkeeperBridgeJIRA.php +++ b/src/applications/doorkeeper/bridge/DoorkeeperBridgeJIRA.php @@ -104,10 +104,7 @@ } $this->fillObjectFromData($obj, $result); - - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $obj->save(); - unset($unguarded); + $this->saveExternalObject($ref, $obj); } }