r108601 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r108600‎ | r108601 | r108602 >
Date:09:05, 11 January 2012
Author:hashar
Status:ok (Comments)
Tags:
Comment:
reverts Concurrency works

trunk is frozen pending stabilisation so we can release MediaWiki 1.19.
Those changes introduces API changes and new SQL tables, so that sounds like
new feature we do not have time to review right now.

Please reapply changes in branches/concurrency and have code review handled
there. Once the branch has been reviewed, please hold. Once trunk is stable
enough and 1.19 got branched, you are welcome to merge the branch in trunk.

Note: we can have a Jenkins jobs setup to run the branch tests if you need.

Reverts:
r108595 r108591 r108585 r108584 108572 r108564 108560 r108559
Modified paths:
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/ConcurrencyCheck.php (deleted) (history)
  • /trunk/phase3/includes/DefaultSettings.php (modified) (history)
  • /trunk/phase3/includes/api/ApiConcurrency.php (deleted) (history)
  • /trunk/phase3/includes/api/ApiMain.php (modified) (history)
  • /trunk/phase3/includes/installer/MysqlUpdater.php (modified) (history)
  • /trunk/phase3/includes/installer/SqliteUpdater.php (modified) (history)
  • /trunk/phase3/maintenance/archives/patch-concurrencycheck.sql (deleted) (history)
  • /trunk/phase3/maintenance/sqlite/archives/patch-concurrencycheck.sql (deleted) (history)
  • /trunk/phase3/maintenance/tables.sql (modified) (history)
  • /trunk/phase3/tests/phpunit/includes/ConcurrencyCheckTest.php (deleted) (history)
  • /trunk/phase3/tests/phpunit/includes/api/ApiConcurrencyTest.php (deleted) (history)

Diff [purge]

Index: trunk/phase3/maintenance/archives/patch-concurrencycheck.sql
@@ -1,25 +0,0 @@
2 -CREATE TABLE /*_*/concurrencycheck (
3 - -- a string describing the resource or application being checked out.
4 - cc_resource_type varchar(255) NOT NULL,
5 -
6 - -- the (numeric) ID of the record that's being checked out.
7 - cc_record int unsigned NOT NULL,
8 -
9 - -- the user who has control of the resource
10 - cc_user int unsigned NOT NULL,
11 -
12 - -- the date/time on which this record expires
13 - cc_expiration varbinary(14) not null
14 -
15 -) /*$wgDBTableOptions*/;
16 -CREATE UNIQUE INDEX /*i*/cc_resource_record ON /*_*/concurrencycheck (cc_resource_type, cc_record);
17 -CREATE INDEX /*i*/cc_user ON /*_*/concurrencycheck (cc_user);
18 -CREATE INDEX /*i*/cc_expiration ON /*_*/concurrencycheck (cc_expiration);
Index: trunk/phase3/maintenance/sqlite/archives/patch-concurrencycheck.sql
@@ -1,25 +0,0 @@
2 -CREATE TABLE /*_*/concurrencycheck (
3 - -- a string describing the resource or application being checked out.
4 - cc_resource_type varchar(255) NOT NULL,
5 -
6 - -- the (numeric) ID of the record that's being checked out.
7 - cc_record int unsigned NOT NULL,
8 -
9 - -- the user who has control of the resource
10 - cc_user int unsigned NOT NULL,
11 -
12 - -- the date/time on which this record expires
13 - cc_expiration varbinary(14) not null
14 -
15 -) /*$wgDBTableOptions*/;
16 -CREATE UNIQUE INDEX /*i*/cc_resource_record ON /*_*/concurrencycheck (cc_resource_type, cc_record);
17 -CREATE INDEX /*i*/cc_user ON /*_*/concurrencycheck (cc_user);
18 -CREATE INDEX /*i*/cc_expiration ON /*_*/concurrencycheck (cc_expiration);
Index: trunk/phase3/maintenance/tables.sql
@@ -1483,31 +1483,4 @@
14841484 -- Should cover *most* configuration - strings, ints, bools, etc.
14851485 CREATE INDEX /*i*/cf_name_value ON /*_*/config (cf_name,cf_value(255));
14861486
1487 -CREATE TABLE /*_*/concurrencycheck (
1488 - -- a string describing the resource or application being checked out.
1489 - cc_resource_type varchar(255) NOT NULL,
1490 -
1491 - -- the (numeric) ID of the record that's being checked out.
1492 - cc_record int unsigned NOT NULL,
1493 -
1494 - -- the user who has control of the resource
1495 - cc_user int unsigned NOT NULL,
1496 -
1497 - -- the date/time on which this record expires
1498 - cc_expiration varbinary(14) not null
1499 -
1500 -) /*$wgDBTableOptions*/;
1501 -CREATE UNIQUE INDEX /*i*/cc_resource_record ON /*_*/concurrencycheck (cc_resource_type, cc_record);
1502 -CREATE INDEX /*i*/cc_user ON /*_*/concurrencycheck (cc_user);
1503 -CREATE INDEX /*i*/cc_expiration ON /*_*/concurrencycheck (cc_expiration);
1504 -
1505 -
15061487 -- vim: sw=2 sts=2 et
Property changes on: trunk/phase3/maintenance/tables.sql
___________________________________________________________________
Modified: svn:mergeinfo
15071488 Reverse-merged /branches/concurrency/maintenance/tables.sql:r108302-108557
Index: trunk/phase3/tests/phpunit/includes/ConcurrencyCheckTest.php
@@ -1,106 +0,0 @@
2 -<?php
3 -
4 -class ConcurrencyCheckTest extends MediaWikiTestCase {
5 - /**
6 - * @var Array of test users
7 - */
8 - public static $users;
9 -
10 - // Prepare test environment
11 -
12 - public function setUp() {
13 - parent::setUp();
14 -
15 - self::$users = array(
16 - 'user1' => new ApiTestUser(
17 - 'Concurrencychecktestuser1',
18 - 'ConcurrencyCheck Test User 1',
19 - 'concurrency_check_test_user_1@example.com',
20 - array()
21 - ),
22 - 'user2' => new ApiTestUser(
23 - 'Concurrencychecktestuser2',
24 - 'ConcurrencyCheck Test User 2',
25 - 'concurrency_check_test_user_2@example.com',
26 - array()
27 - ),
28 - );
29 -
30 - // turn on memcached for this test.
31 - // if no memcached is present, this still works fine.
32 - global $wgMainCacheType, $wgConcurrency;
33 - $this->oldcache = $wgMainCacheType;
34 - $wgMainCacheType = CACHE_MEMCACHED;
35 - $wgConcurrency['ExpirationMin'] = -60; // negative numbers are needed for testing
36 - }
37 -
38 - public function tearDown() {
39 - // turn off caching again.
40 - global $wgMainCacheType;
41 - $wgMainCacheType = $this->oldcache;
42 -
43 - parent::tearDown();
44 - }
45 -
46 - // Actual tests from here down
47 -
48 - public function testCheckoutCheckin() {
49 - $first = new ConcurrencyCheck( 'CCUnitTest', self::$users['user1']->user );
50 - $second = new ConcurrencyCheck( 'CCUnitTest', self::$users['user2']->user );
51 - $testKey = 1337;
52 -
53 - // clean up after any previously failed tests
54 - $first->checkin($testKey);
55 - $second->checkin($testKey);
56 -
57 - // tests
58 - /* turning these tests off per robla, since I need to go home.
59 - $this->assertTrue( $first->checkout($testKey), "Initial checkout" );
60 - $this->assertTrue( $first->checkout($testKey), "Cache hit" );
61 - $this->assertFalse( $second->checkout($testKey), "Checkout of locked resource fails as different user" );
62 - $this->assertTrue( $first->checkout($testKey), "Checkout of locked resource succeeds as original user" );
63 - $this->assertFalse( $second->checkin($testKey), "Checkin of locked resource fails as different user" );
64 - $this->assertTrue( $first->checkin($testKey), "Checkin of locked resource succeeds as original user" );
65 - $second->setExpirationTime(-5);
66 - $this->assertTrue( $second->checkout($testKey), "Checked-in resource is now available to second user" );
67 - $second->setExpirationTime();
68 - $this->assertTrue( $first->checkout($testKey), "Checkout of expired resource succeeds as first user");
69 - $this->assertTrue( $second->checkout($testKey, true), "Checkout override" );
70 - $this->assertFalse( $first->checkout($testKey), "Checkout of overriden resource fails as different user" );
71 -
72 -
73 - // cleanup
74 - $this->assertTrue( $second->checkin($testKey), "Checkin of record with changed ownership" );
75 - */
76 - }
77 -
78 - public function testExpire() {
79 - $cc = new ConcurrencyCheck( 'CCUnitTest', self::$users['user1']->user );
80 - $cc->setExpirationTime(-1);
81 - $cc->checkout( 1338 ); // these numbers are test record ids.
82 - $cc->checkout( 1339 );
83 - $cc->setExpirationTime();
84 - $cc->checkout( 13310 );
85 -
86 - // tests
87 - $this->assertEquals( 2, $cc->expire(), "Resource expiration" );
88 - $this->assertTrue( $cc->checkin( 13310 ), "Checkin succeeds after expiration" );
89 - }
90 -
91 - public function testStatus() {
92 - $cc = new ConcurrencyCheck( 'CCUnitTest', self::$users['user1']->user );
93 - $cc->checkout( 1337 );
94 - $cc->checkout( 1338 );
95 - $cc->setExpirationTime(-5);
96 - $cc->checkout( 1339 );
97 - $cc->setExpirationTime();
98 -
99 - // tests
100 - $output = $cc->status( array( 1337, 1338, 1339, 13310 ) );
101 - $this->assertEquals( true, is_array( $output ), "Status returns values" );
102 - $this->assertEquals( 4, count( $output ), "Output has the correct number of records" );
103 - $this->assertEquals( 'valid', $output[1337]['status'], "Current checkouts are listed as valid");
104 - $this->assertEquals( 'invalid', $output[1339]['status'], "Expired checkouts are invalid");
105 - $this->assertEquals( 'invalid', $output[13310]['status'], "Missing checkouts are invalid");
106 - }
107 -}
Index: trunk/phase3/tests/phpunit/includes/api/ApiConcurrencyTest.php
@@ -1,172 +0,0 @@
2 -<?php
3 -
4 -class ApiConcurrencyTest extends ApiTestCase {
5 - /**
6 - * @var Array of test users
7 - */
8 - public static $users;
9 -
10 - // Prepare test environment
11 -
12 - function setUp() {
13 - parent::setUp();
14 -
15 - self::$users['one'] = new ApiTestUser(
16 - 'ApitestuserA',
17 - 'Api Test UserA',
18 - 'api_test_userA@example.com',
19 - array()
20 - );
21 -
22 - self::$users['two'] = new ApiTestUser(
23 - 'ApitestuserB',
24 - 'Api Test UserB',
25 - 'api_test_userB@example.com',
26 - array()
27 - );
28 - }
29 -
30 - public function tearDown() {
31 - parent::tearDown();
32 - }
33 -
34 - function testLogin() {
35 -
36 - $sessionArray = array();
37 -
38 - foreach ( self::$users as $key => $user ) {
39 -
40 - $params = array(
41 - 'action' => 'login',
42 - 'lgname' => $user->username,
43 - 'lgpassword' => $user->password
44 - );
45 - list( $result, , $session ) = $this->doApiRequest( $params );
46 - $this->assertArrayHasKey( "login", $result );
47 - $this->assertArrayHasKey( "result", $result['login'] );
48 - $this->assertEquals( "NeedToken", $result['login']['result'] );
49 - $token = $result['login']['token'];
50 -
51 - $params = array(
52 - 'action' => 'login',
53 - 'lgtoken' => $token,
54 - 'lgname' => $user->username,
55 - 'lgpassword' => $user->password
56 - );
57 - list( $result, , $session ) = $this->doApiRequest( $params, $session );
58 - $this->assertArrayHasKey( "login", $result );
59 - $this->assertArrayHasKey( "result", $result['login'] );
60 - $this->assertEquals( "Success", $result['login']['result'] );
61 - $this->assertArrayHasKey( 'lgtoken', $result['login'] );
62 -
63 - $this->assertNotEmpty( $session, 'API Login must return a session' );
64 -
65 - $sessionArray[$key] = $session;
66 -
67 - }
68 -
69 - return $sessionArray;
70 -
71 - }
72 -
73 - /**
74 - * @depends testLogin
75 - */
76 - function testCheckOut( $sessionArray ) {
77 -
78 - global $wgUser;
79 -
80 - $wgUser = self::$users['one']->user;
81 - /* commenting these out since i need to go home and they're breakin CI. See commit summary for details.
82 -
83 - list( $result, , $session ) = $this->doApiRequestWithToken( array(
84 - 'action' => 'concurrency',
85 - 'ccaction' => 'checkout',
86 - 'record' => 1,
87 - 'resourcetype' => 'responding-to-moodbar-feedback'), $sessionArray['one'], self::$users['one']->user );
88 -
89 - $this->assertEquals( "success", $result['concurrency']['result'] );
90 -
91 - $wgUser = self::$users['two']->user;
92 -
93 - list( $result, , $session ) = $this->doApiRequestWithToken( array(
94 - 'action' => 'concurrency',
95 - 'ccaction' => 'checkout',
96 - 'record' => 1,
97 - 'resourcetype' => 'responding-to-moodbar-feedback'), $sessionArray['two'], self::$users['two']->user );
98 -
99 - $this->assertEquals( "failure", $result['concurrency']['result'] );
100 -
101 - list( $result, , $session ) = $this->doApiRequestWithToken( array(
102 - 'action' => 'concurrency',
103 - 'ccaction' => 'checkout',
104 - 'record' => 2,
105 - 'resourcetype' => 'responding-to-moodbar-feedback'), $sessionArray['two'], self::$users['two']->user );
106 -
107 - $this->assertEquals( "success", $result['concurrency']['result'] );
108 - */
109 - }
110 -
111 - /**
112 - * @depends testLogin
113 - */
114 - function testCheckIn( $sessionArray ) {
115 -
116 - global $wgUser;
117 -
118 - $wgUser = self::$users['one']->user;
119 - /* commenting these out since i need to go home and they're breakin CI. See commit summary for details.
120 -
121 - list( $result, , $session ) = $this->doApiRequestWithToken( array(
122 - 'action' => 'concurrency',
123 - 'ccaction' => 'checkin',
124 - 'record' => 1,
125 - 'resourcetype' => 'responding-to-moodbar-feedback'), $sessionArray['one'], self::$users['one']->user );
126 -
127 - $this->assertEquals( "success", $result['concurrency']['result'] );
128 -
129 - list( $result, , $session ) = $this->doApiRequestWithToken( array(
130 - 'action' => 'concurrency',
131 - 'ccaction' => 'checkin',
132 - 'record' => 2,
133 - 'resourcetype' => 'responding-to-moodbar-feedback'), $sessionArray['one'], self::$users['one']->user );
134 -
135 - $this->assertEquals( "failure", $result['concurrency']['result'] );
136 -
137 - $wgUser = self::$users['two']->user;
138 -
139 - list( $result, , $session ) = $this->doApiRequestWithToken( array(
140 - 'action' => 'concurrency',
141 - 'ccaction' => 'checkin',
142 - 'record' => 2,
143 - 'resourcetype' => 'responding-to-moodbar-feedback'), $sessionArray['two'], self::$users['two']->user );
144 -
145 - $this->assertEquals( "success", $result['concurrency']['result'] );
146 - */
147 - }
148 -
149 - /**
150 - * @depends testLogin
151 - */
152 - function testInvalidCcacton( $sessionArray ) {
153 - $exception = false;
154 - try {
155 - global $wgUser;
156 -
157 - $wgUser = self::$users['one']->user;
158 -
159 - list( $result, , $session ) = $this->doApiRequestWithToken( array(
160 - 'action' => 'concurrency',
161 - 'ccaction' => 'checkinX',
162 - 'record' => 1,
163 - 'resourcetype' => 'responding-to-moodbar-feedback'), $sessionArray['one'], self::$users['one']->user );
164 - } catch ( UsageException $e ) {
165 - $exception = true;
166 - $this->assertEquals("Unrecognized value for parameter 'ccaction': checkinX",
167 - $e->getMessage() );
168 - }
169 - $this->assertTrue( $exception, "Got exception" );
170 -
171 - }
172 -
173 -}
Index: trunk/phase3/includes/ConcurrencyCheck.php
@@ -1,358 +0,0 @@
2 -<?php
3 -
4 -/**
5 - * Class for cooperative locking of web resources
6 - *
7 - * Each resource is identified by a combination of the "resource type" (the application, the type
8 - * of content, etc), and the resource's primary key or some other unique numeric ID.
9 - *
10 - * Currently, a resource can only be checked out by a single user. Other attempts to check it out result
11 - * in the checkout failing. In the future, an option for multiple simulataneous checkouts could be added
12 - * without much trouble.
13 - *
14 - * This could be done with named locks, except then it would be impossible to build a list of all the
15 - * resources currently checked out for a given application. There's no good way to construct a query
16 - * that answers the question, "What locks do you have starting with [foo]" This could be done really well
17 - * with a concurrent, reliable, distributed key/value store, but we don't have one of those right now.
18 - *
19 - * @author Ian Baker <ian@wikimedia.org>
20 - */
21 -class ConcurrencyCheck {
22 -
23 - protected $expirationTime;
24 -
25 - /**
26 - * @var User
27 - */
28 - protected $user;
29 -
30 - /**
31 - * Constructor
32 - *
33 - * @var $resourceType String The calling application or type of resource, conceptually like a namespace
34 - * @var $user User object, the current user
35 - * @var $expirationTime Integer (optional) How long should a checkout last, in seconds
36 - */
37 - public function __construct( $resourceType, $user, $expirationTime = null ) {
38 -
39 - // All database calls are to the master, since the whole point of this class is maintaining
40 - // concurrency. Most reads should come from cache anyway.
41 - $this->dbw = wfGetDb( DB_MASTER );
42 -
43 - $this->user = $user;
44 - // TODO: create a registry of all valid resourceTypes that client app can add to.
45 - $this->resourceType = $resourceType;
46 - $this->setExpirationTime( $expirationTime );
47 - }
48 -
49 - /**
50 - * Check out a resource. This establishes an atomically generated, cooperative lock
51 - * on a key. The lock is tied to the current user.
52 - *
53 - * @var $record Integer containing the record id to check out
54 - * @var $override Boolean (optional) describing whether to override an existing checkout
55 - * @return boolean
56 - */
57 - public function checkout( $record, $override = null ) {
58 - global $wgMemc;
59 - $this->validateId( $record );
60 - $dbw = $this->dbw;
61 - $userId = $this->user->getId();
62 - $cacheKey = wfMemcKey( 'concurrencycheck', $this->resourceType, $record );
63 -
64 - // when operating with a single memcached cluster, it's reasonable to check the cache here.
65 - global $wgConcurrency;
66 - if( $wgConcurrency['TrustMemc'] ) {
67 - $cached = $wgMemc->get( $cacheKey );
68 - if( $cached ) {
69 - if( ! $override && $cached['userId'] != $userId && $cached['expiration'] > time() ) {
70 - // this is already checked out.
71 - return false;
72 - }
73 - }
74 - }
75 -
76 - // attempt an insert, check success (this is atomic)
77 - $insertError = null;
78 - $res = $dbw->insert(
79 - 'concurrencycheck',
80 - array(
81 - 'cc_resource_type' => $this->resourceType,
82 - 'cc_record' => $record,
83 - 'cc_user' => $userId,
84 - 'cc_expiration' => wfTimestamp( TS_MW, time() + $this->expirationTime ),
85 - ),
86 - __METHOD__,
87 - array( 'IGNORE' )
88 - );
89 -
90 - // if the insert succeeded, checkout is done.
91 - if( $dbw->affectedRows() === 1 ) {
92 - // delete any existing cache key. can't create a new key here
93 - // since the insert didn't happen inside a transaction.
94 - $wgMemc->delete( $cacheKey );
95 - return true;
96 - }
97 -
98 - // if the insert failed, it's necessary to check the expiration.
99 - // here, check by deleting, since that permits the use of write locks
100 - // (SELECT..LOCK IN SHARE MODE), rather than read locks (SELECT..FOR UPDATE)
101 - $dbw->begin();
102 - $dbw->delete(
103 - 'concurrencycheck',
104 - array(
105 - 'cc_resource_type' => $this->resourceType,
106 - 'cc_record' => $record,
107 - '(cc_user = ' . $userId . ' OR cc_expiration <= ' . $dbw->addQuotes(wfTimestamp( TS_MW )) . ')', // only the owner can perform a checkin
108 - ),
109 - __METHOD__,
110 - array()
111 - );
112 -
113 - // delete failed: not checked out by current user, checkout is unexpired, override is unset
114 - if( $dbw->affectedRows() !== 1 && ! $override) {
115 - // fetch the existing data to cache it
116 - $row = $dbw->selectRow(
117 - 'concurrencycheck',
118 - array( '*' ),
119 - array(
120 - 'cc_resource_type' => $this->resourceType,
121 - 'cc_record' => $record,
122 - ),
123 - __METHOD__,
124 - array()
125 - );
126 -
127 - // this was a cache miss. populate the cache with data from the db.
128 - // cache is set to expire at the same time as the checkout, since it'll become invalid then anyway.
129 - // inside this transaction, a row-level lock is established which ensures cache concurrency
130 - $wgMemc->set( $cacheKey, array( 'userId' => $row->cc_user, 'expiration' => wfTimestamp( TS_UNIX, $row->cc_expiration ) ), wfTimestamp( TS_UNIX, $row->cc_expiration ) - time() );
131 - $dbw->rollback();
132 - return false;
133 - }
134 -
135 - $expiration = time() + $this->expirationTime;
136 -
137 - // delete succeeded, insert a new row.
138 - // replace is used here to support the override parameter
139 - $res = $dbw->replace(
140 - 'concurrencycheck',
141 - array( 'cc_resource_type', 'cc_record' ),
142 - array(
143 - 'cc_resource_type' => $this->resourceType,
144 - 'cc_record' => $record,
145 - 'cc_user' => $userId,
146 - 'cc_expiration' => wfTimestamp( TS_MW, $expiration ),
147 - ),
148 - __METHOD__
149 - );
150 -
151 - // cache the result.
152 - $wgMemc->set( $cacheKey, array( 'userId' => $userId, 'expiration' => $expiration ), $this->expirationTime );
153 -
154 - $dbw->commit();
155 - return true;
156 - }
157 -
158 - /**
159 - * Check in a resource. Only works if the resource is checked out by the current user.
160 - *
161 - * @var $record Integer containing the record id to checkin
162 - * @return Boolean
163 - */
164 - public function checkin( $record ) {
165 - global $wgMemc;
166 - $this->validateId( $record );
167 - $dbw = $this->dbw;
168 - $userId = $this->user->getId();
169 - $cacheKey = wfMemcKey( 'concurrencycheck', $this->resourceType, $record );
170 -
171 - $dbw->delete(
172 - 'concurrencycheck',
173 - array(
174 - 'cc_resource_type' => $this->resourceType,
175 - 'cc_record' => $record,
176 - 'cc_user' => $userId, // only the owner can perform a checkin
177 - ),
178 - __METHOD__,
179 - array()
180 - );
181 -
182 - // check row count (this is atomic, select would not be)
183 - if( $dbw->affectedRows() === 1 ) {
184 - $wgMemc->delete( $cacheKey );
185 - return true;
186 - }
187 -
188 - return false;
189 - }
190 -
191 - /**
192 - * Remove all expired checkouts.
193 - *
194 - * @return Integer describing the number of records expired.
195 - */
196 - public function expire() {
197 - // TODO: run this in a few other places that db access happens, to make sure the db stays non-crufty.
198 - $dbw = $this->dbw;
199 - $now = time();
200 -
201 - // remove the rows from the db. trust memcached to expire the cache.
202 - $dbw->delete(
203 - 'concurrencycheck',
204 - array(
205 - 'cc_expiration <= ' . $dbw->addQuotes( wfTimestamp( TS_MW, $now ) ),
206 - ),
207 - __METHOD__,
208 - array()
209 - );
210 -
211 - // return the number of rows removed.
212 - return $dbw->affectedRows();
213 - }
214 -
215 - public function status( $keys ) {
216 - global $wgMemc, $wgDBtype;
217 - $dbw = $this->dbw;
218 - $now = time();
219 -
220 - $checkouts = array();
221 - $toSelect = array();
222 -
223 - // validate keys, attempt to retrieve from cache.
224 - foreach( $keys as $key ) {
225 - $this->validateId( $key );
226 -
227 - $cached = $wgMemc->get( wfMemcKey( 'concurrencycheck', $this->resourceType, $key ) );
228 - if( $cached && $cached['expiration'] > $now ) {
229 - $checkouts[$key] = array(
230 - 'status' => 'valid',
231 - 'cc_resource_type' => $this->resourceType,
232 - 'cc_record' => $key,
233 - 'cc_user' => $cached['userId'],
234 - 'cc_expiration' => wfTimestamp( TS_MW, $cached['expiration'] ),
235 - 'cache' => 'cached',
236 - );
237 - } else {
238 - $toSelect[] = $key;
239 - }
240 - }
241 -
242 - // if there were cache misses...
243 - if( $toSelect ) {
244 - // If it's time to go to the database, go ahead and expire old rows.
245 - $this->expire();
246 -
247 -
248 - // Why LOCK IN SHARE MODE, you might ask? To avoid a race condition: Otherwise, it's possible for
249 - // a checkin and/or checkout to occur between this select and the value being stored in cache, which
250 - // makes for an incorrect cache. This, in turn, could make checkout() above (which uses the cache)
251 - // function incorrectly.
252 - //
253 - // Another option would be to run the select, then check each row in-turn before setting the cache
254 - // key using either SELECT (with LOCK IN SHARE MODE) or UPDATE that checks a timestamp (and which
255 - // would establish the same lock). That method would mean smaller, quicker locks, but more overall
256 - // database overhead.
257 - //
258 - // It appears all the DBMSes we use support LOCK IN SHARE MODE, but if that's not the case, the second
259 - // solution above could be implemented instead.
260 - $queryParams = array();
261 - if( $wgDBtype === 'mysql' ) {
262 - $queryParamsp[] = 'LOCK IN SHARE MODE';
263 -
264 - // the transaction seems incongruous, I know, but it's to keep the cache update atomic.
265 - $dbw->begin();
266 - }
267 -
268 - $res = $dbw->select(
269 - 'concurrencycheck',
270 - array( '*' ),
271 - array(
272 - 'cc_resource_type' => $this->resourceType,
273 - 'cc_record' => $toSelect,
274 - 'cc_expiration > ' . $dbw->addQuotes( wfTimestamp( TS_MW ) ),
275 - ),
276 - __METHOD__,
277 - $queryParams
278 - );
279 -
280 - while( $res && $record = $res->fetchRow() ) {
281 - $record['status'] = 'valid';
282 - $checkouts[ $record['cc_record'] ] = $record;
283 -
284 - // TODO: implement strategy #2 above, determine which DBMSes need which method.
285 - // for now, disable adding to cache here for databases that don't support read locking
286 - if( $wgDBtype !== 'mysql' ) {
287 - // safe to store values since this is inside the transaction
288 - $wgMemc->set(
289 - wfMemcKey( 'concurrencycheck', $this->resourceType, $record['cc_record'] ),
290 - array( 'userId' => $record['cc_user'], 'expiration' => wfTimestamp( TS_UNIX, $record['cc_expiration'] ) ),
291 - wfTimestamp( TS_UNIX, $record['cc_expiration'] ) - time()
292 - );
293 - }
294 - }
295 -
296 - if( $wgDBtype === 'mysql' ) {
297 - // end the transaction.
298 - $dbw->rollback();
299 - }
300 - }
301 -
302 - // if a key was passed in but has no (unexpired) checkout, include it in the
303 - // result set to make things easier and more consistent on the client-side.
304 - foreach( $keys as $key ) {
305 - if( ! array_key_exists( $key, $checkouts ) ) {
306 - $checkouts[$key]['status'] = 'invalid';
307 - }
308 - }
309 -
310 - return $checkouts;
311 - }
312 -
313 - public function listCheckouts() {
314 - // TODO: fill in the function that lets you get the complete set of checkouts for a given application.
315 - }
316 -
317 - /**
318 - * @param $user user
319 - */
320 - public function setUser( $user ) {
321 - $this->user = $user;
322 - }
323 -
324 - public function setExpirationTime( $expirationTime = null ) {
325 - global $wgConcurrency;
326 -
327 - // check to make sure the time is a number
328 - // negative number are allowed, though mostly only used for testing
329 - if( $expirationTime && (int) $expirationTime == $expirationTime ) {
330 - if( $expirationTime > $wgConcurrency['ExpirationMax'] ) {
331 - $this->expirationTime = $wgConcurrency['ExpirationMax']; // if the number is too high, limit it to the max value.
332 - } elseif ( $expirationTime < $wgConcurrency['ExpirationMin'] ) {
333 - $this->expirationTime = $wgConcurrency['ExpirationMin']; // low limit, default -1 min
334 - } else {
335 - $this->expirationTime = $expirationTime; // the amount of time before a checkout expires.
336 - }
337 - } else {
338 - $this->expirationTime = $wgConcurrency['ExpirationDefault']; // global default is 15 mins.
339 - }
340 - }
341 -
342 - /**
343 - * Check to make sure a record ID is numeric, throw an exception if not.
344 - *
345 - * @var $record Integer
346 - * @throws ConcurrencyCheckBadRecordIdException
347 - * @return boolean
348 - */
349 - private static function validateId ( $record ) {
350 - if( (int) $record !== $record || $record <= 0 ) {
351 - throw new ConcurrencyCheckBadRecordIdException( 'Record ID ' . $record . ' must be a positive integer' );
352 - }
353 -
354 - // TODO: add a hook here for client-side validation.
355 - return true;
356 - }
357 -}
358 -
359 -class ConcurrencyCheckBadRecordIdException extends MWException {}
Index: trunk/phase3/includes/installer/SqliteUpdater.php
@@ -71,7 +71,6 @@
7272 array( 'modifyField', 'user', 'ug_group', 'patch-ug_group-length-increase.sql' ),
7373 array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ),
7474 array( 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.sql' ),
75 - array( 'addTable', 'concurrencycheck', 'patch-concurrencycheck.sql'),
7675 );
7776 }
7877
Index: trunk/phase3/includes/installer/MysqlUpdater.php
@@ -192,7 +192,6 @@
193193 array( 'modifyField', 'user', 'ug_group', 'patch-ug_group-length-increase.sql' ),
194194 array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ),
195195 array( 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.sql' ),
196 - array( 'addTable', 'concurrencycheck', 'patch-concurrencycheck.sql'),
197196 );
198197 }
199198
Index: trunk/phase3/includes/AutoLoader.php
@@ -43,7 +43,6 @@
4444 'ChannelFeed' => 'includes/Feed.php',
4545 'Collation' => 'includes/Collation.php',
4646 'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php',
47 - 'ConcurrencyCheck' => 'includes/ConcurrencyCheck.php',
4847 'ConfEditor' => 'includes/ConfEditor.php',
4948 'ConfEditorParseError' => 'includes/ConfEditor.php',
5049 'ConfEditorToken' => 'includes/ConfEditor.php',
@@ -53,6 +52,7 @@
5453 'DeferredUpdates' => 'includes/DeferredUpdates.php',
5554 'DerivativeRequest' => 'includes/WebRequest.php',
5655 'DiffHistoryBlob' => 'includes/HistoryBlob.php',
 56+
5757 'DoubleReplacer' => 'includes/StringUtils.php',
5858 'DummyLinker' => 'includes/Linker.php',
5959 'Dump7ZipOutput' => 'includes/Export.php',
@@ -271,7 +271,6 @@
272272 'ApiBase' => 'includes/api/ApiBase.php',
273273 'ApiBlock' => 'includes/api/ApiBlock.php',
274274 'ApiComparePages' => 'includes/api/ApiComparePages.php',
275 - 'ApiConcurrency' => 'includes/api/ApiConcurrency.php',
276275 'ApiDelete' => 'includes/api/ApiDelete.php',
277276 'ApiDisabled' => 'includes/api/ApiDisabled.php',
278277 'ApiEditPage' => 'includes/api/ApiEditPage.php',
Property changes on: trunk/phase3/includes/AutoLoader.php
___________________________________________________________________
Modified: svn:mergeinfo
279278 Reverse-merged /branches/concurrency/includes/AutoLoader.php:r108302-108557
Index: trunk/phase3/includes/api/ApiConcurrency.php
@@ -1,105 +0,0 @@
2 -<?php
3 -
4 -/**
5 - * API module that handles cooperative locking of web resources
6 - */
7 -class ApiConcurrency extends ApiBase {
8 - public function __construct( $main, $action ) {
9 - parent::__construct( $main, $action );
10 - }
11 -
12 - public function execute() {
13 - global $wgUser;
14 -
15 - $this->checkPermission( $wgUser );
16 -
17 - $params = $this->extractRequestParams();
18 -
19 - $res = array();
20 -
21 - $concurrencyCheck = new ConcurrencyCheck( $params['resourcetype'], $wgUser );
22 -
23 - switch ( $params['ccaction'] ) {
24 - case 'checkout':
25 - case 'checkin':
26 - if ( $concurrencyCheck->$params['ccaction']( $params['record'] ) ) {
27 - $res['result'] = 'success';
28 - } else {
29 - $res['result'] = 'failure';
30 - }
31 - break;
32 -
33 - default:
34 - ApiBase::dieDebug( __METHOD__, "Unhandled concurrency action: {$params['ccaction']}" );
35 - }
36 -
37 - $this->getResult()->addValue( null, $this->getModuleName(), $res );
38 - }
39 -
40 - public function mustBePosted() {
41 - return true;
42 - }
43 -
44 - public function isWriteMode() {
45 - return true;
46 - }
47 -
48 - public function getAllowedParams() {
49 - return array(
50 - 'resourcetype' => array(
51 - ApiBase::PARAM_TYPE => 'string',
52 - ApiBase::PARAM_REQUIRED => true
53 - ),
54 - 'record' => array(
55 - ApiBase::PARAM_TYPE => 'integer',
56 - ApiBase::PARAM_REQUIRED => true
57 - ),
58 - 'token' => null,
59 - 'expiry' => array(
60 - ApiBase::PARAM_TYPE => 'integer'
61 - ),
62 - 'ccaction' => array(
63 - ApiBase::PARAM_REQUIRED => true,
64 - ApiBase::PARAM_TYPE => array(
65 - 'checkout',
66 - 'checkin',
67 - ),
68 - ),
69 - );
70 - }
71 -
72 - public function getParamDescription() {
73 - return array(
74 - 'resourcetype' => 'the resource type for concurrency check',
75 - 'record' => 'an unique identifier for a record of the defined resource type',
76 - 'expiry' => 'the time interval for expiration',
77 - 'ccaction' => 'the action for concurrency check',
78 - );
79 - }
80 -
81 - public function getDescription() {
82 - return 'Get/Set a concurrency check for a web resource type';
83 - }
84 -
85 - public function needsToken() {
86 - return true;
87 - }
88 -
89 - public function getTokenSalt() {
90 - return '';
91 - }
92 -
93 - public function getVersion() {
94 - return __CLASS__ . ': $Id$';
95 - }
96 -
97 - private function checkPermission( $user ) {
98 - if ( $user->isAnon() ) {
99 - $this->dieUsage( "You don't have permission to do that", 'permission-denied' );
100 - }
101 - if ( $user->isBlocked( false ) ) {
102 - $this->dieUsageMsg( array( 'blockedtext' ) );
103 - }
104 - }
105 -
106 -}
Index: trunk/phase3/includes/api/ApiMain.php
@@ -79,7 +79,6 @@
8080 'patrol' => 'ApiPatrol',
8181 'import' => 'ApiImport',
8282 'userrights' => 'ApiUserrights',
83 - 'concurrency' => 'ApiConcurrency',
8483 );
8584
8685 /**
Index: trunk/phase3/includes/DefaultSettings.php
@@ -5717,16 +5717,6 @@
57185718 $wgDBtestpassword = '';
57195719
57205720 /**
5721 - * ConcurrencyCheck keeps track of which web resources are in use, for producing higher-quality UI
5722 - */
5723 -$wgConcurrency = array();
5724 -$wgConcurrency['ExpirationDefault'] = 60 * 15; // Default checkout duration. 15 minutes.
5725 -$wgConcurrency['ExpirationMax'] = 60 * 30; // Maximum possible checkout duration. 30 minutes.
5726 -$wgConcurrency['ExpirationMin'] = 1; // Minimum possible checkout duration. Negative is possible for testing if you want.
5727 -$wgConcurrency['TrustMemc'] = true; // If running in an environment with multiple discrete caches, set to false.
5728 -
5729 -
5730 -/**
57315721 * For really cool vim folding this needs to be at the end:
57325722 * vim: foldmarker=@{,@} foldmethod=marker
57335723 * @}

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r108559MERGE branches/concurrency 108301:108557 into trunkraindrift23:03, 10 January 2012
r108560Add svn:keywords Id...reedy23:12, 10 January 2012
r108564Updated to use correct cross-db timestamps and date functions...raindrift23:42, 10 January 2012
r108572add js resource for concurrency api, check method for resource checkin & chec...rmoen00:17, 11 January 2012
r108584Fixed concurrency issues related to mysql default locking mode, per Roan's co...raindrift01:10, 11 January 2012
r108585sqlite doesn't actually support read locks. added a workaround until i can d...raindrift01:27, 11 January 2012
r108591sqlite needs its timestamps quoted...raindrift02:02, 11 January 2012
r108595Commenting these tests out so that CI can run, since I need to leave and nobo...raindrift02:48, 11 January 2012

Comments

#Comment by Hashar (talk | contribs)   09:08, 11 January 2012

Revision manually added as follow up of r108572 r108560.

Status & tagging log