]> git.immae.eu Git - github/shaarli/Shaarli.git/blobdiff - application/bookmark/BookmarkIO.php
Fix: soft fail if the mutex is not working
[github/shaarli/Shaarli.git] / application / bookmark / BookmarkIO.php
index 6bf7f3654ebfdd41f87b9468f00c135c2725e9da..8439d470da21fdff3d0b7da95230de0594e5a341 100644 (file)
@@ -1,7 +1,12 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Shaarli\Bookmark;
 
+use malkusch\lock\exception\LockAcquireException;
+use malkusch\lock\mutex\Mutex;
+use malkusch\lock\mutex\NoMutex;
 use Shaarli\Bookmark\Exception\DatastoreNotInitializedException;
 use Shaarli\Bookmark\Exception\EmptyDataStoreException;
 use Shaarli\Bookmark\Exception\NotWritableDataStoreException;
@@ -27,11 +32,14 @@ class BookmarkIO
      */
     protected $conf;
 
+
+    /** @var Mutex */
+    protected $mutex;
+
     /**
      * string Datastore PHP prefix
      */
     protected static $phpPrefix = '<?php /* ';
-
     /**
      * string Datastore PHP suffix
      */
@@ -42,16 +50,21 @@ class BookmarkIO
      *
      * @param ConfigManager $conf instance
      */
-    public function __construct($conf)
+    public function __construct(ConfigManager $conf, Mutex $mutex = null)
     {
+        if ($mutex === null) {
+            // This should only happen with legacy classes
+            $mutex = new NoMutex();
+        }
         $this->conf = $conf;
         $this->datastore = $conf->get('resource.datastore');
+        $this->mutex = $mutex;
     }
 
     /**
      * Reads database from disk to memory
      *
-     * @return BookmarkArray instance
+     * @return Bookmark[]
      *
      * @throws NotWritableDataStoreException    Data couldn't be loaded
      * @throws EmptyDataStoreException          Datastore file exists but does not contain any bookmark
@@ -67,11 +80,16 @@ class BookmarkIO
             throw new NotWritableDataStoreException($this->datastore);
         }
 
+        $content = null;
+        $this->synchronized(function () use (&$content) {
+            $content = file_get_contents($this->datastore);
+        });
+
         // Note that gzinflate is faster than gzuncompress.
         // See: http://www.php.net/manual/en/function.gzdeflate.php#96439
         $links = unserialize(gzinflate(base64_decode(
-            substr(file_get_contents($this->datastore),
-                strlen(self::$phpPrefix), -strlen(self::$phpSuffix)))));
+            substr($content, strlen(self::$phpPrefix), -strlen(self::$phpSuffix))
+        )));
 
         if (empty($links)) {
             if (filesize($this->datastore) > 100) {
@@ -86,7 +104,7 @@ class BookmarkIO
     /**
      * Saves the database from memory to disk
      *
-     * @param BookmarkArray $links instance.
+     * @param Bookmark[] $links
      *
      * @throws NotWritableDataStoreException the datastore is not writable
      */
@@ -95,14 +113,35 @@ class BookmarkIO
         if (is_file($this->datastore) && !is_writeable($this->datastore)) {
             // The datastore exists but is not writeable
             throw new NotWritableDataStoreException($this->datastore);
-        } else if (!is_file($this->datastore) && !is_writeable(dirname($this->datastore))) {
+        } elseif (!is_file($this->datastore) && !is_writeable(dirname($this->datastore))) {
             // The datastore does not exist and its parent directory is not writeable
             throw new NotWritableDataStoreException(dirname($this->datastore));
         }
 
-        file_put_contents(
-            $this->datastore,
-            self::$phpPrefix.base64_encode(gzdeflate(serialize($links))).self::$phpSuffix
-        );
+        $data = self::$phpPrefix . base64_encode(gzdeflate(serialize($links))) . self::$phpSuffix;
+
+        $this->synchronized(function () use ($data) {
+            file_put_contents(
+                $this->datastore,
+                $data
+            );
+        });
+    }
+
+    /**
+     * Wrapper applying mutex to provided function.
+     * If the lock can't be acquired (e.g. some shared hosting provider), we execute the function without mutex.
+     *
+     * @see https://github.com/shaarli/Shaarli/issues/1650
+     *
+     * @param callable $function
+     */
+    protected function synchronized(callable $function): void
+    {
+        try {
+            $this->mutex->synchronized($function);
+        } catch (LockAcquireException $exception) {
+            $function();
+        }
     }
 }