-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathConcurrentBuildLockService.php
More file actions
73 lines (63 loc) · 3.81 KB
/
ConcurrentBuildLockService.php
File metadata and controls
73 lines (63 loc) · 3.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?php
namespace Flowpack\DecoupledContentStore\Core;
use Flowpack\DecoupledContentStore\Core\Domain\ValueObject\ContentReleaseIdentifier;
use Flowpack\DecoupledContentStore\Core\Infrastructure\RedisClientManager;
use Flowpack\DecoupledContentStore\PrepareContentRelease\Infrastructure\RedisContentReleaseService;
use Neos\Flow\Annotations as Flow;
/**
* We usually rely on prunner to ensure that only one build per workspace is running at any given time.
*
* However, when running in a cloud environment with no shared storage, the prunner data folder is not shared between
* instances. In this case, during a deployment, two containers run concurrently, with two separate prunner instances
* (the old and the new one), which do not see each other.
*
* We could fix this in prunner itself, but this would be a bigger undertaking (different storage backends for prunner),
* or we can work around this in DecoupledContentStore. This is what this class does.
*
* ## Main Idea
*
* - We use a special redis key "contentStore:concurrentBuildLock" which is set to the current being-built release ID in
* `./flow contentReleasePrepare:ensureAllOtherInProgressContentReleasesWillBeTerminated`
* - In the "Enumerate" and "Render" phases, we periodically check whether the concurrentBuildLock is set to the currently
* in-progress content release. If NO, we abort.
*
* @Flow\Scope("singleton")
*/
class ConcurrentBuildLockService
{
private const CONTENT_STORE_CONCURRENT_BUILD_LOCK = 'contentStore:concurrentBuildLocks';
/**
* @Flow\Inject
* @var RedisClientManager
*/
protected $redisClientManager;
/**
* @Flow\Inject
* @var RedisContentReleaseService
*/
protected $redisContentReleaseService;
public function ensureAllOtherInProgressContentReleasesWillBeTerminated(ContentReleaseIdentifier $contentReleaseIdentifier): void
{
$metadata = $this->redisContentReleaseService->fetchMetadataForContentRelease($contentReleaseIdentifier);
$this->redisClientManager->getPrimaryRedis()->hSet(self::CONTENT_STORE_CONCURRENT_BUILD_LOCK, $metadata->getWorkspaceName(), (string)$contentReleaseIdentifier);
}
public function assertNoOtherContentReleaseWasStarted(ContentReleaseIdentifier $contentReleaseIdentifier): void
{
$metadata = $this->redisContentReleaseService->fetchMetadataForContentRelease($contentReleaseIdentifier);
$concurrentBuildLockStrings = $this->redisClientManager->getPrimaryRedis()->hGetAll(self::CONTENT_STORE_CONCURRENT_BUILD_LOCK);
$concurrentBuildLockStringForWorkspace = $concurrentBuildLockStrings[$metadata->getWorkspaceName()] ?? null;
if (!$concurrentBuildLockStringForWorkspace) {
echo '!!!!! Hard-aborting the current job ' . $contentReleaseIdentifier->getIdentifier() . ' because the concurrentBuildLock does not exist.' . "\n\n";
echo "This should never happen for correctly configured jobs (that run after prepare_finished).\n\n";
exit(1);
}
$concurrentBuildLock = ContentReleaseIdentifier::fromString($concurrentBuildLockStringForWorkspace);
if (!$contentReleaseIdentifier->equals($concurrentBuildLock)) {
// the concurrent build lock is different (i.e. newer) than our currently-running content release.
// Thus, we abort the in-progress content release as quickly as we can - by DYING.
echo '!!!!! Hard-aborting the current job ' . $contentReleaseIdentifier->getIdentifier() . ' because the concurrentBuildLock for workspace "' . $metadata->getWorkspaceName() . '" contains ' . $concurrentBuildLock->getIdentifier() . "\n\n";
echo "This is no error during deployment, but should never happen outside a deployment.\n\n It can only happen if two prunner instances run concurrently.\n\n";
exit(1);
}
}
}