Passed
Push — master ( d72d9f...e878c0 )
by Morris
37:10 queued 26:13
created

MemcacheLockingProvider::releaseLock()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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