Completed
Push — master ( 3c9b12...422c80 )
by Morris
19:07
created

MemcacheLockingProvider::changeLock()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 14

Duplication

Lines 14
Ratio 100 %

Importance

Changes 0
Metric Value
cc 5
nc 5
nop 2
dl 14
loc 14
rs 9.4888
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
			$newValue = 0;
94
			if ($this->getOwnSharedLockCount($path) === 1) {
95
				$removed = $this->memcache->cad($path, 1); // if we're the only one having a shared lock we can remove it in one go
96
				if (!$removed) { //someone else also has a shared lock, decrease only
97
					$newValue = $this->memcache->dec($path);
98
				}
99
			} else {
100
				// if we own more than one lock ourselves just decrease
101
				$newValue = $this->memcache->dec($path);
102
			}
103
104
			// if we somehow release more locks then exists, reset the lock
105
			if ($newValue < 0) {
106
				$this->memcache->cad($path, $newValue);
107
			}
108
		} else if ($type === self::LOCK_EXCLUSIVE) {
109
			$this->memcache->cad($path, 'exclusive');
110
		}
111
		$this->markRelease($path, $type);
112
	}
113
114
	/**
115
	 * Change the type of an existing lock
116
	 *
117
	 * @param string $path
118
	 * @param int $targetType self::LOCK_SHARED or self::LOCK_EXCLUSIVE
119
	 * @throws \OCP\Lock\LockedException
120
	 */
121 View Code Duplication
	public function changeLock(string $path, int $targetType) {
122
		if ($targetType === self::LOCK_SHARED) {
123
			if (!$this->memcache->cas($path, 'exclusive', 1)) {
124
				throw new LockedException($path, null, $this->getExistingLockForException($path));
125
			}
126
		} else if ($targetType === self::LOCK_EXCLUSIVE) {
127
			// we can only change a shared lock to an exclusive if there's only a single owner of the shared lock
128
			if (!$this->memcache->cas($path, 1, 'exclusive')) {
129
				throw new LockedException($path, null, $this->getExistingLockForException($path));
130
			}
131
		}
132
		$this->setTTL($path);
133
		$this->markChange($path, $targetType);
134
	}
135
136
	private function getExistingLockForException($path) {
137
		$existing = $this->memcache->get($path);
138
		if (!$existing) {
139
			return 'none';
140
		} else if ($existing === 'exclusive') {
141
			return $existing;
142
		} else {
143
			return $existing . ' shared locks';
144
		}
145
	}
146
}
147