Completed
Push — master ( 6d5f44...7f5567 )
by Morris
54:19 queued 34:30
created

getExistingLockForException()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
/**
4
 * @copyright Copyright (c) 2016, ownCloud, Inc.
5
 *
6
 * @author Robin Appelman <[email protected]>
7
 *
8
 * @license AGPL-3.0
9
 *
10
 * This code is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Affero General Public License, version 3,
12
 * as published by the Free Software Foundation.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License, version 3,
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
21
 *
22
 */
23
24
namespace OC\Lock;
25
26
use OCP\IMemcacheTTL;
27
use OCP\Lock\LockedException;
28
use OCP\IMemcache;
29
30
class MemcacheLockingProvider extends AbstractLockingProvider {
31
	/**
32
	 * @var \OCP\IMemcache
33
	 */
34
	private $memcache;
35
36
	/**
37
	 * @param \OCP\IMemcache $memcache
38
	 * @param int $ttl
39
	 */
40
	public function __construct(IMemcache $memcache, int $ttl = 3600) {
41
		$this->memcache = $memcache;
42
		$this->ttl = $ttl;
43
	}
44
45
	private function setTTL(string $path) {
46
		if ($this->memcache instanceof IMemcacheTTL) {
47
			$this->memcache->setTTL($path, $this->ttl);
48
		}
49
	}
50
51
	/**
52
	 * @param string $path
53
	 * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
54
	 * @return bool
55
	 */
56
	public function isLocked(string $path, int $type): bool {
57
		$lockValue = $this->memcache->get($path);
58
		if ($type === self::LOCK_SHARED) {
59
			return $lockValue > 0;
60
		} else if ($type === self::LOCK_EXCLUSIVE) {
61
			return $lockValue === 'exclusive';
62
		} else {
63
			return false;
64
		}
65
	}
66
67
	/**
68
	 * @param string $path
69
	 * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
70
	 * @throws \OCP\Lock\LockedException
71
	 */
72 View Code Duplication
	public function acquireLock(string $path, int $type) {
73
		if ($type === self::LOCK_SHARED) {
74
			if (!$this->memcache->inc($path)) {
75
				throw new LockedException($path, null, $this->getExistingLockForException($path));
76
			}
77
		} else {
78
			$this->memcache->add($path, 0);
79
			if (!$this->memcache->cas($path, 0, 'exclusive')) {
80
				throw new LockedException($path, null, $this->getExistingLockForException($path));
81
			}
82
		}
83
		$this->setTTL($path);
84
		$this->markAcquire($path, $type);
85
	}
86
87
	/**
88
	 * @param string $path
89
	 * @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
90
	 */
91
	public function releaseLock(string $path, int $type) {
92
		if ($type === self::LOCK_SHARED) {
93
			if ($this->getOwnSharedLockCount($path) === 1) {
94
				$removed = $this->memcache->cad($path, 1); // if we're the only one having a shared lock we can remove it in one go
95
				if (!$removed) { //someone else also has a shared lock, decrease only
96
					$this->memcache->dec($path);
97
				}
98
			} else {
99
				// if we own more than one lock ourselves just decrease
100
				$this->memcache->dec($path);
101
			}
102
		} else if ($type === self::LOCK_EXCLUSIVE) {
103
			$this->memcache->cad($path, 'exclusive');
104
		}
105
		$this->markRelease($path, $type);
106
	}
107
108
	/**
109
	 * Change the type of an existing lock
110
	 *
111
	 * @param string $path
112
	 * @param int $targetType self::LOCK_SHARED or self::LOCK_EXCLUSIVE
113
	 * @throws \OCP\Lock\LockedException
114
	 */
115 View Code Duplication
	public function changeLock(string $path, int $targetType) {
116
		if ($targetType === self::LOCK_SHARED) {
117
			if (!$this->memcache->cas($path, 'exclusive', 1)) {
118
				throw new LockedException($path, null, $this->getExistingLockForException($path));
119
			}
120
		} else if ($targetType === self::LOCK_EXCLUSIVE) {
121
			// we can only change a shared lock to an exclusive if there's only a single owner of the shared lock
122
			if (!$this->memcache->cas($path, 1, 'exclusive')) {
123
				throw new LockedException($path, null, $this->getExistingLockForException($path));
124
			}
125
		}
126
		$this->setTTL($path);
127
		$this->markChange($path, $targetType);
128
	}
129
130
	private function getExistingLockForException($path) {
131
		$existing = $this->memcache->get($path);
132
		if (!$existing) {
133
			return 'none';
134
		} else if ($existing === 'exclusive') {
135
			return $existing;
136
		} else {
137
			return $existing . ' shared locks';
138
		}
139
	}
140
}
141