3
* Gallery - a web based photo album viewer and editor
4
* Copyright (C) 2000-2007 Bharat Mediratta
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or (at
9
* your option) any later version.
11
* This program is distributed in the hope that it will be useful, but
12
* WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
* General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
21
GalleryCoreApi::requireOnce('modules/core/classes/DatabaseLockSystem.class');
22
GalleryCoreApi::requireOnce('modules/core/classes/FlockLockSystem.class');
26
* @package GalleryCore
28
* @author Bharat Mediratta <bharat@menalto.com>
29
* @version $Revision: 15799 $
31
class LockTest extends GalleryTestCase {
33
function LockTest($methodName) {
34
$this->GalleryTestCase($methodName);
41
list ($ret, $this->_album) = $this->_createRandomAlbum($this->_getRootId());
43
return $this->failWithStatus($ret);
45
$this->_markForCleanup($this->_album);
47
list ($ret, $this->_item) = $this->_createRandomDataItem($this->_album->getId());
49
return $this->failWithStatus($ret);
52
$storage =& $gallery->getStorage();
53
$ret = $storage->checkPoint();
55
return $this->failWithStatus($ret);
59
function _tryAllLockSystems($testFunction) {
61
$testFunction = '_' . $testFunction;
63
$gallery->_lockSystem = new DatabaseLockSystem();
64
$this->$testFunction('dblock');
65
$gallery->_lockSystem->releaseAllLocks();
66
$gallery->_lockSystem->releaseQueue();
68
$gallery->_lockSystem = new FlockLockSystem();
69
$this->$testFunction('flock');
70
$gallery->_lockSystem->releaseAllLocks();
71
$gallery->_lockSystem->releaseQueue();
74
function testOneWriteLock() {
75
return $this->_tryAllLockSystems('testOneWriteLock');
78
function _testOneWriteLock($lockSystem) {
79
$targetId = $this->_item->getId();
81
list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($targetId);
83
return $this->failWithStatus($ret);
86
$this->assert(is_int($lockId), "$lockSystem: lock id is not an integer");
88
$this->assert(GalleryCoreApi::isWriteLocked($targetId), $lockSystem);
90
$ret = GalleryCoreApi::releaseLocks($lockId);
92
return $this->failWithStatus($ret);
96
function testManyWriteLocks() {
97
return $this->_tryAllLockSystems('testManyWriteLocks');
100
function _testManyWriteLocks($lockSystem) {
103
$ids = array($gallery->getActiveUserId(), $this->_getRootId(), $this->_item->getId());
105
list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($ids);
107
return $this->failWithStatus($ret);
110
$this->assert(is_int($lockId), "$lockSystem: lock id is not an integer");
112
foreach ($ids as $id) {
113
$this->assert(GalleryCoreApi::isWriteLocked($id), "$lockSystem: $id");
116
$ret = GalleryCoreApi::releaseLocks($lockId);
118
return $this->failWithStatus($ret);
122
function testOneReadLock() {
123
return $this->_tryAllLockSystems('testOneReadLock');
126
function _testOneReadLock($lockSystem) {
127
$targetId = $this->_album->getId();
129
list ($ret, $lockId) = GalleryCoreApi::acquireReadLock($targetId);
131
return $this->failWithStatus($ret);
134
$this->assert(is_int($lockId), "$lockSystem: lock id is not an integer");
136
$this->assert(GalleryCoreApi::isReadLocked($targetId), $lockSystem);
138
$ret = GalleryCoreApi::releaseLocks($lockId);
140
return $this->failWithStatus($ret);
144
function testManyReadLocks() {
145
return $this->_tryAllLockSystems('testManyReadLocks');
148
function _testManyReadLocks($lockSystem) {
151
$ids = array($gallery->getActiveUserId(), $this->_getRootId(), $this->_item->getId());
153
list ($ret, $lockId) = GalleryCoreApi::acquireReadLock($ids);
155
return $this->failWithStatus($ret);
158
$this->assert(is_int($lockId), "$lockSystem: lock id is not an integer");
160
foreach ($ids as $id) {
161
$this->assert(GalleryCoreApi::isReadLocked($id), "$lockSystem: $id");
164
$ret = GalleryCoreApi::releaseLocks($lockId);
166
return $this->failWithStatus($ret);
170
function testAcquireReadLockParents() {
171
return $this->_tryAllLockSystems('testAcquireReadLockParents');
174
function _testAcquireReadLockParents($lockSystem) {
177
/* Create a hierarchy */
178
$parentId = $this->_album->getId();
179
for ($i = 0; $i < 3; $i++) {
180
list ($ret, $this->_subAlbums[$i]) = $this->_createRandomAlbum($parentId);
182
return $this->failWithStatus($ret);
184
$parentId = $this->_subAlbums[$i]->getId();
187
list ($ret, $lockId) = GalleryCoreApi::acquireReadLockParents(
188
$this->_subAlbums[2]->getId());
190
return $this->failWithStatus($ret);
193
$this->assert(is_int($lockId), "$lockSystem: lock id is not an integer");
195
/* Verify that they're all locked except the item itself */
196
$this->assert(!GalleryCoreApi::isReadLocked($this->_subAlbums[2]->getId()),
198
$this->assert(GalleryCoreApi::isReadLocked($this->_subAlbums[1]->getId()),
200
$this->assert(GalleryCoreApi::isReadLocked($this->_subAlbums[0]->getId()),
202
$this->assert(GalleryCoreApi::isReadLocked($this->_album->getId()), "$lockSystem: p");
203
$this->assert(GalleryCoreApi::isReadLocked($this->_getRootId()), "$lockSystem: r");
206
$ret = GalleryCoreApi::releaseLocks($lockId);
208
return $this->failWithStatus($ret);
212
function testAcquireReadLockParentsOnRoot() {
213
return $this->_tryAllLockSystems('testAcquireReadLockParentsOnRoot');
216
function _testAcquireReadLockParentsOnRoot($lockSystem) {
219
list ($ret, $lockId) = GalleryCoreApi::acquireReadLockParents($this->_getRootId());
221
return $this->failWithStatus($ret);
224
/* Verify that the root is not read locked */
225
$this->assert(!GalleryCoreApi::isReadLocked($this->_getRootId()),
226
"$lockSystem: Root should not be locked");
229
$ret = GalleryCoreApi::releaseLocks($lockId);
231
return $this->failWithStatus($ret);
236
* Test acquiring a write lock on an entity that has already been read locked. We don't
237
* currently require this ability, but it could be useful if adding or deleting an item from an
238
* album triggered an update to that album. Uncomment this test if this type of locking is ever
239
* used. (Note that Windows has problems doing this with flock based locks, so we may avoid
240
* any functionality that needs this.)
243
function testReadLockToWriteLock() {
244
return $this->_tryAllLockSystems('testReadLockToWriteLock');
248
function _testReadLockToWriteLock($lockSystem) {
249
/* Read lock parents, then attempt to write lock direct parent */
250
list ($ret, $lockId) = GalleryCoreApi::acquireReadLockParents($this->_item->getId());
252
return $this->failWithStatus($ret);
255
foreach (array($this->_getRootId(), $this->_album->getId()) as $id) {
256
$this->assert(GalleryCoreApi::isReadLocked($id), "$lockSystem: read lock: $id");
259
list ($ret, $writeLockId) = GalleryCoreApi::acquireWriteLock($this->_album->getId());
261
return $this->failWithStatus($ret);
264
$this->assert(is_int($writeLockId), "$lockSystem: lock id is not an integer");
266
$this->assert(GalleryCoreApi::isWriteLocked($this->_album->getId()),
267
"$lockSystem: write lock");
268
$this->assert(GalleryCoreApi::isReadLocked($this->_getRootId()), "$lockSystem: read lock");
271
$ret = GalleryCoreApi::releaseLocks(array($lockId, $writeLockId));
273
return $this->failWithStatus($ret);
278
* Verify that releaseLocks accepts sloppy input (null entries or empty list) so that error
279
* handling code can pass in whatever array it has and not worry about cleaning it up to have
282
function testReleaseNull() {
283
return $this->_tryAllLockSystems('testReleaseNull');
286
function _testReleaseNull($lockSystem) {
287
$targetId = $this->_item->getId();
290
list ($ret, $lockIds[]) = GalleryCoreApi::acquireWriteLock($targetId);
292
return $this->failWithStatus($ret);
295
$this->assert(GalleryCoreApi::isWriteLocked($targetId), $lockSystem);
298
$ret = GalleryCoreApi::releaseLocks($lockIds);
300
$this->assert(false, $lockSystem);
301
return $this->failWithStatus($ret);
304
$this->assert(!GalleryCoreApi::isWriteLocked($targetId), $lockSystem . ' unlock');
307
/* See comment above */
308
function testReleaseEmpty() {
309
return $this->_tryAllLockSystems('testReleaseEmpty');
312
function _testReleaseEmpty($lockSystem) {
313
$ret = GalleryCoreApi::releaseLocks(array());
315
$this->assert(false, $lockSystem);
316
return $this->failWithStatus($ret);
321
* Verify that locks are not being released before commit on transactional databases.
322
* http://sourceforge.net/tracker/index.php?func=detail&aid=1345508&group_id=7130&atid=107130
324
function testReleaseLocksNotBeforeCommit() {
326
$storage =& $gallery->getStorage();
328
list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($this->_item->getId());
330
return $this->failWithStatus($ret);
333
$this->_item->setTitle($this->_item->getTitle() . 'changed');
334
$ret = $this->_item->save();
336
GalleryCoreApi::releaseLocks($lockId);
337
return $this->failWithStatus($ret);
340
$ret = GalleryCoreApi::releaseLocks($lockId);
342
return $this->failWithStatus($ret);
345
/* Temporarily clear the existing lock information to simulate another request */
346
$queuedLocks = $gallery->_lockSystem->_releaseQueue;
347
$locks = $gallery->_lockSystem->_locks;
348
$gallery->_lockSystem->_releaseQueue = array();
349
$gallery->_lockSystem->_locks = array();
351
/* This should time out if the previous lock is queued */
352
list ($ret, $secondLockId) = GalleryCoreApi::acquireWriteLock($this->_item->getId(), 5);
354
$this->assertEquals($ret->getErrorCode(), GALLERY_ERROR | ERROR_LOCK_TIMEOUT,
355
'The lock acquisition should have timed out.');
356
if ($ret->getErrorCode() != (GALLERY_ERROR | ERROR_LOCK_TIMEOUT)) {
357
return $this->failWithStatus($ret);
360
/* Lock successfully acquired, this should only happen on nontransactional databases */
361
$this->assert(!$storage->isTransactional(), 'Lock acquired on transactional database.');
363
$ret = GalleryCoreApi::releaseLocks($secondLockId);
365
return $this->failWithStatus($ret);
369
/* Restore lock information and force their release. */
370
$gallery->_lockSystem->_releaseQueue = $queuedLocks;
371
$gallery->_lockSystem->_locks = $locks;
372
$ret = $gallery->_lockSystem->releaseQueue();
374
return $this->failWithStatus($ret);
378
function testRelockRead() {
379
/* Read lock, release, relock before committing transaction */
381
$storage =& $gallery->getStorage();
383
list ($ret, $lockId) = GalleryCoreApi::acquireReadLock($this->_item->getId());
385
return $this->failWithStatus($ret);
388
$ret = GalleryCoreApi::releaseLocks($lockId);
390
return $this->failWithStatus($ret);
393
$this->assert(count($gallery->_lockSystem->_releaseQueue) == 1
394
|| !$storage->isTransactional(), 'release first lock');
396
list ($ret, $lockId) = GalleryCoreApi::acquireReadLock($this->_item->getId());
398
return $this->failWithStatus($ret);
401
$this->assert(is_int($lockId), 'lock id is not an integer');
403
/* Release queue unaffected */
404
$this->assert(count($gallery->_lockSystem->_releaseQueue) == 1
405
|| !$storage->isTransactional(), 'get second lock');
407
$ret = GalleryCoreApi::releaseLocks($lockId);
409
return $this->failWithStatus($ret);
412
/* Release queue unaffected, multiple read locks are ok */
413
$this->assert(count($gallery->_lockSystem->_releaseQueue) == 2
414
|| !$storage->isTransactional(), 'release second lock');
417
function testWriteThenRead() {
418
/* Write lock, release, then acquire read lock before committing transaction */
420
$storage =& $gallery->getStorage();
422
list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($this->_item->getId());
424
return $this->failWithStatus($ret);
427
$ret = GalleryCoreApi::releaseLocks($lockId);
429
return $this->failWithStatus($ret);
432
$this->assert(count($gallery->_lockSystem->_releaseQueue) == 1
433
|| !$storage->isTransactional(), 'release write lock');
435
list ($ret, $lockId) = GalleryCoreApi::acquireReadLock($this->_item->getId());
437
return $this->failWithStatus($ret);
440
$this->assert(is_int($lockId), 'lock id is not an integer');
442
/* Had to release that write lock in order to get new read lock */
443
$this->assert(empty($gallery->_lockSystem->_releaseQueue), 'release queue should be empty');
445
$ret = GalleryCoreApi::releaseLocks($lockId);
447
return $this->failWithStatus($ret);
451
function testWriteThenReadPartial() {
452
/* Write lock, release, then acquire a read lock for one entity in that write lock */
454
$storage =& $gallery->getStorage();
456
list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock(array($this->_album->getId(),
457
$this->_item->getId()));
459
return $this->failWithStatus($ret);
462
$ret = GalleryCoreApi::releaseLocks($lockId);
464
return $this->failWithStatus($ret);
467
$this->assert((count($gallery->_lockSystem->_releaseQueue) == 1
468
&& count($gallery->_lockSystem->_releaseQueue[$lockId]['ids']) == 2)
469
|| !$storage->isTransactional(), 'release write lock');
471
list ($ret, $newLockId) = GalleryCoreApi::acquireReadLock($this->_item->getId());
473
return $this->failWithStatus($ret);
476
$this->assert(is_int($newLockId), 'lock id is not an integer');
478
/* Removed single entity from pending write lock in order to get new read lock */
479
$this->assert((count($gallery->_lockSystem->_releaseQueue) == 1
480
&& count($gallery->_lockSystem->_releaseQueue[$lockId]['ids']) == 1)
481
|| !$storage->isTransactional(), 'pending write lock should have one entity');
483
$ret = GalleryCoreApi::releaseLocks($newLockId);
485
return $this->failWithStatus($ret);
489
function testRelockWrite() {
490
/* Write lock, release, relock before committing transaction */
492
$storage =& $gallery->getStorage();
494
list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($this->_item->getId());
496
return $this->failWithStatus($ret);
499
$ret = GalleryCoreApi::releaseLocks($lockId);
501
return $this->failWithStatus($ret);
504
$this->assert(count($gallery->_lockSystem->_releaseQueue) == 1
505
|| !$storage->isTransactional(), 'release first lock');
507
list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($this->_item->getId());
509
return $this->failWithStatus($ret);
512
$this->assert(is_int($lockId), 'lock id is not an integer');
514
/* Real lock moved to new lockId which clears out queued lock */
515
$this->assert(empty($gallery->_lockSystem->_releaseQueue), 'release queue should be empty');
516
$ret = GalleryCoreApi::releaseLocks($lockId);
518
return $this->failWithStatus($ret);
522
function testRelockWritePartial() {
523
/* Write lock, release, then acquire new write lock for one id in previous lock */
525
$storage =& $gallery->getStorage();
527
list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock(array($this->_album->getId(),
528
$this->_item->getId()));
530
return $this->failWithStatus($ret);
533
$ret = GalleryCoreApi::releaseLocks($lockId);
535
return $this->failWithStatus($ret);
538
$this->assert((count($gallery->_lockSystem->_releaseQueue) == 1
539
&& count($gallery->_lockSystem->_releaseQueue[$lockId]['ids']) == 2)
540
|| !$storage->isTransactional(), 'release first lock');
542
list ($ret, $newLockId) = GalleryCoreApi::acquireWriteLock($this->_item->getId());
544
return $this->failWithStatus($ret);
547
$this->assert(is_int($newLockId), 'lock id is not an integer');
549
/* One id moved to new lockId */
550
$this->assert((count($gallery->_lockSystem->_releaseQueue) == 1
551
&& count($gallery->_lockSystem->_releaseQueue[$lockId]['ids']) == 1)
552
|| !$storage->isTransactional(), 'acquire new lock');
554
$ret = GalleryCoreApi::releaseLocks($newLockId);
556
return $this->failWithStatus($ret);
560
function testRelockWriteMore() {
561
/* Write lock, release, then acquire new write lock with more ids than previous lock */
563
$storage =& $gallery->getStorage();
565
list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($this->_item->getId());
567
return $this->failWithStatus($ret);
570
$ret = GalleryCoreApi::releaseLocks($lockId);
572
return $this->failWithStatus($ret);
575
$this->assert((count($gallery->_lockSystem->_releaseQueue) == 1
576
&& count($gallery->_lockSystem->_releaseQueue[$lockId]['ids']) == 1)
577
|| !$storage->isTransactional(), 'release first lock');
579
list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock(array($this->_album->getId(),
580
$this->_item->getId()));
582
return $this->failWithStatus($ret);
585
$this->assert(is_int($lockId), 'lock id is not an integer');
587
/* Real lock moved to new lockId which clears out queued lock */
588
$this->assert(empty($gallery->_lockSystem->_releaseQueue), 'release queue should be empty');
590
$ret = GalleryCoreApi::releaseLocks($lockId);
592
return $this->failWithStatus($ret);
596
function testReadThenWrite() {
597
/* Read lock, release, then acquire write lock before committing transaction */
599
$storage =& $gallery->getStorage();
601
list ($ret, $lockId) = GalleryCoreApi::acquireReadLock($this->_item->getId());
603
return $this->failWithStatus($ret);
606
$ret = GalleryCoreApi::releaseLocks($lockId);
608
return $this->failWithStatus($ret);
611
$this->assert(count($gallery->_lockSystem->_releaseQueue) == 1
612
|| !$storage->isTransactional(), 'release read lock');
614
list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($this->_item->getId());
616
return $this->failWithStatus($ret);
619
$this->assert(is_int($lockId), 'lock id is not an integer');
621
/* Had to release that read lock in order to get new write lock */
622
$this->assert(empty($gallery->_lockSystem->_releaseQueue), 'release queue should be empty');
624
$ret = GalleryCoreApi::releaseLocks($lockId);
626
return $this->failWithStatus($ret);
630
function testReadThenWritePartial() {
631
/* Read lock, release, then acquire a write lock for one entity in that read lock */
633
$storage =& $gallery->getStorage();
635
list ($ret, $lockId) = GalleryCoreApi::acquireReadLock(array($this->_item->getId(),
636
$this->_album->getId()));
638
return $this->failWithStatus($ret);
641
$ret = GalleryCoreApi::releaseLocks($lockId);
643
return $this->failWithStatus($ret);
646
$this->assert((count($gallery->_lockSystem->_releaseQueue) == 1
647
&& count($gallery->_lockSystem->_releaseQueue[$lockId]['ids']) == 2)
648
|| !$storage->isTransactional(), 'release read lock');
650
list ($ret, $newLockId) = GalleryCoreApi::acquireWriteLock($this->_item->getId());
652
return $this->failWithStatus($ret);
655
$this->assert(is_int($newLockId), 'lock id is not an integer');
657
/* Removed single entity from pending read lock in order to get new write lock */
658
$this->assert((count($gallery->_lockSystem->_releaseQueue) == 1
659
&& count($gallery->_lockSystem->_releaseQueue[$lockId]['ids']) == 1)
660
|| !$storage->isTransactional(), 'pending read lock should have one entity');
662
$ret = GalleryCoreApi::releaseLocks($newLockId);
664
return $this->failWithStatus($ret);
668
function testReadReadAgainThenWrite() {
669
return $this->_tryAllLockSystems('testReadReadAgainThenWrite');
672
/* Acquire a write lock when item is in multiple locks in the release queue */
673
function _testReadReadAgainThenWrite($lockSystem) {
675
$storage =& $gallery->getStorage();
677
$itemId = $this->_item->getId();
678
list ($ret, $lockId) = GalleryCoreApi::acquireReadLock($itemId);
680
return $this->failWithStatus($ret);
683
$ret = GalleryCoreApi::releaseLocks($lockId);
685
return $this->failWithStatus($ret);
688
list ($ret, $lockId) = GalleryCoreApi::acquireReadLock($itemId);
690
return $this->failWithStatus($ret);
693
$ret = GalleryCoreApi::releaseLocks($lockId);
695
return $this->failWithStatus($ret);
698
$this->assert(count($gallery->_lockSystem->_releaseQueue) == 2
699
|| !$storage->isTransactional(), "$lockSystem: release read lock");
701
list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($itemId);
703
return $this->failWithStatus($ret);
706
$this->assert(is_int($lockId), "$lockSystem: lock id is not an integer");
708
/* Had to release that read locks in order to get new write lock */
709
$this->assert(empty($gallery->_lockSystem->_releaseQueue),
710
"$lockSystem: release queue should be empty");
712
$ret = GalleryCoreApi::releaseLocks($lockId);
714
return $this->failWithStatus($ret);