com web/rmtools: Rewrite for atomic rw for the db file: include/Branch.php
| From: | Anatol Belski | Date: | Tue, 19 Sep 2017 10:11:09 +0000 |
| Subject: | com web/rmtools: Rewrite for atomic rw for the db file: include/Branch.php | ||
| Groups: | php.webmaster | ||
| Request: | Send a blank email to php-webmaster+get-26413@lists.php.net to get a copy of this message | ||
Commit: b8f48f651c5e80d2a877316becc75a6a25ee6c6f
Author: Anatol Belski <ab@php.net> Tue, 19 Sep 2017 12:11:09 +0200
Parents: 6659d824d1a42db91fcf8aaa117abae3f4dfc316
Branches: master
Link: http://git.php.net/?p=web/rmtools.git;a=commitdiff;h=b8f48f651c5e80d2a877316becc75a6a25ee6c6f
Log:
Rewrite for atomic rw for the db file
Changed paths:
M include/Branch.php
Diff:
diff --git a/include/Branch.php b/include/Branch.php
index c26cc21..b87c38d 100644
--- a/include/Branch.php
+++ b/include/Branch.php
@@ -12,6 +12,7 @@ class Branch {
public $config;
private $repo;
public $db_path;
+ protected $db_fd;
public $data = NULL;
public function __construct($config_path)
@@ -25,7 +26,7 @@ class Branch {
$this->repo->setBranch($this->config->getRepoBranch());
$this->db_path = __DIR__ . '/../data/db/' . $this->config->getName() .
'.json';
- $this->data = $this->readdata();
+ $this->data = $this->readData();
}
protected function getEmptyData()
@@ -40,22 +41,84 @@ class Branch {
return $data;
}
- protected function readData()
+ /* If both read and write are requested, read and yield data first. The fd
+ is still open and the exclusive lock is held. Call the function once
+ more to write. */
+ protected function atomicDataRW($read = true, $write = false)
{
- $fd = fopen($this->db_path, "rb");
- if ($fd) {
- flock($fd, LOCK_SH);
+ if ($write) {
+ $open_mode = "cb+";
+ $lock_mode = LOCK_EX;
+ } else {
+ $open_mode = "rb";
+ $lock_mode = LOCK_SH;
+ }
+
+ if (!$this->db_fd) {
+ $this->db_fd = fopen($this->db_path, $open_mode);
+ if (!$this->db_fd) {
+ $this->db_fd = NULL;
+ throw new \Exception("Failed to open {$this->db_path}.");
+ }
+ flock($this->db_fd, $lock_mode);
+ }
+
+ if ($read) {
$j = "";
- while(!feof($fd)) {
- $j .= fread($fd, 1024);
+ while(!feof($this->db_fd)) {
+ $j .= fread($this->db_fd, 1024);
}
$data = json_decode($j);
- flock($fd, LOCK_UN);
- fclose($fd);
- } else {
+
+ if (!$write) {
+ flock($this->db_fd, LOCK_UN);
+ fclose($this->db_fd);
+ $this->db_fd = NULL;
+ }
+ return $data;
+ }
+
+ if ($write) {
+ $json = json_encode($this->data, JSON_PRETTY_PRINT);
+ $to_write = strlen($json);
+ $wrote = 0;
+
+ rewind($this->db_fd);
+ do {
+ $got = fwrite($this->db_fd, substr($json, $wrote));
+ if (false == $got) {
+ break;
+ }
+ $wrote += $got;
+ } while ($wrote < $to_write);
+
+ if ($to_write !== $wrote) {
+ flock($this->db_fd, LOCK_UN);
+ fclose($this->db_fd);
+ $this->db_fd = NULL;
+ throw new Exception("Couldn't write '{$this->db_path}'");
+ }
+
+ flock($this->db_fd, LOCK_UN);
+ fclose($this->db_fd);
+ $this->db_fd = NULL;
+
+ return $wrote > 0;
+ }
+ }
+
+ protected function readData()
+ {
+ try {
+ $data = $this->atomicDataRW(true, false);
+ } catch (\Exception $e) {
$data = $this->getEmptyData();
+ } finally {
+ if (!$data) {
+ $data = $this->getEmptyData();
+ }
}
-
+
if ($data->build_num > self::REQUIRED_BUILDS_NUM) {
throw new \Exception("Inconsistent db, build number can't be
{$data->build_num}.");
}
@@ -65,9 +128,7 @@ class Branch {
protected function writeData()
{
- /* This might be an issue under high concurrency. */
- $json = json_encode($this->data, JSON_PRETTY_PRINT);
- return file_put_contents($this->db_path, $json, LOCK_EX);
+ return $this->atomicDataRW(false, true);
}
private function addBuildList($build_name = NULL)
@@ -105,21 +166,16 @@ class Branch {
public function update($build_name = NULL)
{
- $fd = fopen($this->db_path, "cb+");
- if (!$fd) {
- throw new \Exception("Failed to open {$this->db_path}.");
- }
- flock($fd, LOCK_EX);
- $j = "";
- while(!feof($fd)) {
- $j .= fread($fd, 1024);
- }
- $got_data = strlen($j) > 0;
- if ($got_data) {
- $this->data = json_decode($j);
- } else {
- $this->data = $this->getEmptyData();
+ try {
+ $data = $this->atomicDataRW(true, true);
+ } catch (\Exception $e) {
+ $data = $this->getEmptyData();
+ } finally {
+ if (!$data) {
+ $data = $this->getEmptyData();
+ }
}
+ $this->data = $data;
$last_id = $this->repo->getLastCommitId();
@@ -137,27 +193,9 @@ class Branch {
$this->addBuildList($build_name);
- $json = json_encode($this->data, JSON_PRETTY_PRINT);
- $to_write = strlen($json);
- $wrote = 0;
- rewind($fd);
- do {
- $got = fwrite($fd, substr($json, $wrote));
- if (false == $got) {
- break;
- }
- $wrote += $got;
- } while ($wrote < $to_write);
+ return $this->atomicDataRW(false, true);
- flock($fd, LOCK_UN);
- fclose($fd);
-
- if ($to_write !== $wrote) {
- throw new Exception("Couldn't cache '{$this->db_path}'");
- }
-
- return true;
}
public function hasUnfinishedBuild()