Skip to content

Commit

Permalink
Introduce flock/semaphore locking mechanisms (#592)
Browse files Browse the repository at this point in the history
* introduce flock/semaphore locking mechanisms

* correct proto-client suggest

* re-order exceptions
  • Loading branch information
dwsupplee authored Jul 17, 2017
1 parent 71de21d commit 330b49e
Show file tree
Hide file tree
Showing 22 changed files with 766 additions and 122 deletions.
5 changes: 2 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@
},
"suggest": {
"google/gax": "Required to support gRPC",
"google/proto-client-php": "Required to support gRPC",
"symfony/lock": "Required for the Spanner cached based session pool. Please require the following commit: 3.3.x-dev#1ba6ac9",
"google/proto-client": "Required to support gRPC",
"phpseclib/phpseclib": "May be used in place of OpenSSL for creating signed Cloud Storage URLs. Please require version ^2."
},
"autoload": {
Expand Down Expand Up @@ -115,4 +114,4 @@
"entry": "ServiceBuilder.php"
}
}
}
}
6 changes: 5 additions & 1 deletion src/Core/Batch/BatchDaemon.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

namespace Google\Cloud\Core\Batch;

use Google\Cloud\Core\SysvTrait;

/**
* An external daemon script for executing the batch jobs.
*
Expand All @@ -32,8 +34,9 @@
*/
class BatchDaemon
{
use SysvTrait;
use BatchDaemonTrait;
use HandleFailureTrait;
use SysvTrait;

/* @var BatchRunner */
private $runner;
Expand All @@ -51,6 +54,7 @@ class BatchDaemon
* Prepare the descriptor spec and install signal handlers.
*
* @param string $entrypoint Daemon's entrypoint script.
* @throws \RuntimeException
*/
public function __construct($entrypoint)
{
Expand Down
47 changes: 47 additions & 0 deletions src/Core/Batch/BatchDaemonTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/**
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Cloud\Core\Batch;

/**
* A utility trait related to BatchDaemon functionality.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
trait BatchDaemonTrait
{
private static $typeDirect = 1;
private static $typeFile = 2;

/**
* Returns whether or not the BatchDaemon is running.
*
* @return bool
*/
private function isDaemonRunning()
{
$isDaemonRunning = filter_var(
getenv('IS_BATCH_DAEMON_RUNNING'),
FILTER_VALIDATE_BOOLEAN
);

return $isDaemonRunning !== false;
}
}
3 changes: 3 additions & 0 deletions src/Core/Batch/BatchRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

namespace Google\Cloud\Core\Batch;

use Google\Cloud\Core\SysvTrait;

/**
* A class for executing jobs in batch.
*
Expand All @@ -27,6 +29,7 @@
*/
class BatchRunner
{
use BatchDaemonTrait;
use SysvTrait;

/**
Expand Down
3 changes: 3 additions & 0 deletions src/Core/Batch/SysvProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

namespace Google\Cloud\Core\Batch;

use Google\Cloud\Core\SysvTrait;

/**
* ProcessItemInterface implementation with SysV IPC message queue.
*
Expand All @@ -27,6 +29,7 @@
*/
class SysvProcessor implements ProcessItemInterface
{
use BatchDaemonTrait;
use SysvTrait;

/* @var array */
Expand Down
120 changes: 120 additions & 0 deletions src/Core/Lock/FlockLock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php
/**
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Cloud\Core\Lock;

/**
* Flock based lock implementation.
*
* @see http://php.net/manual/en/function.flock.php
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
class FlockLock implements LockInterface
{
use LockTrait;

const FILE_PATH_TEMPLATE = '%s/%s.lock';

/**
* @var string
*/
private $filePath;

/**
* @var resource|null
*/
private $handle;

/**
* @param string $fileName The name of the file to use as a lock.
* @throws \InvalidArgumentException If an invalid fileName is provided.
*/
public function __construct($fileName)
{
if (!is_string($fileName)) {
throw new \InvalidArgumentException('$fileName must be a string.');
}

$this->filePath = sprintf(
self::FILE_PATH_TEMPLATE,
sys_get_temp_dir(),
$fileName
);
}

/**
* Acquires a lock that will block until released.
*
* @return bool
* @throws \RuntimeException If the lock fails to be acquired.
*/
public function acquire()
{
if ($this->handle) {
return true;
}

$this->handle = $this->initializeHandle();

if (!flock($this->handle, LOCK_EX)) {
fclose($this->handle);
$this->handle = null;

throw new \RuntimeException('Failed to acquire lock.');
}

return true;
}

/**
* Releases the lock.
*
* @throws \RuntimeException If the lock fails to release.
*/
public function release()
{
if ($this->handle) {
$released = flock($this->handle, LOCK_UN);
fclose($this->handle);
$this->handle = null;

if (!$released) {
throw new \RuntimeException('Failed to release lock.');
}
}
}

/**
* Initializes the handle.
*
* @return resource
* @throws \RuntimeException If the lock file fails to open.
*/
private function initializeHandle()
{
$handle = @fopen($this->filePath, 'c');

if (!$handle) {
throw new \RuntimeException('Failed to open lock file.');
}

return $handle;
}
}
2 changes: 1 addition & 1 deletion src/Core/Lock/LockInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ public function release();
/**
* Execute a callable within a lock.
*
* @param callable $func The callable to execute.
* @return mixed
* @throws \RuntimeException
*/
public function synchronize(callable $func);
}
68 changes: 68 additions & 0 deletions src/Core/Lock/LockTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php
/**
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Cloud\Core\Lock;

/**
* Utility trait for locks.
*/
trait LockTrait
{
/**
* Acquires a lock that will block until released.
*
* @return bool
* @throws \RuntimeException
*/
abstract public function acquire();

/**
* Releases the lock.
*
* @throws \RuntimeException
*/
abstract public function release();

/**
* Execute a callable within a lock. If an exception is caught during
* execution of the callable the lock will first be released before throwing
* it.
*
* @param callable $func The callable to execute.
* @return mixed
*/
public function synchronize(callable $func)
{
$result = null;
$exception = null;

if ($this->acquire()) {
try {
$result = $func();
} catch (\Exception $ex) {
$exception = $ex;
}
$this->release();
}

if ($exception) {
throw $exception;
}

return $result;
}
}
Loading

0 comments on commit 330b49e

Please sign in to comment.