When running lock.test locally, I've been noticing the "Lock extended by this request" assertion fails some of the time, without any apparent reason. The assertions are as follows:
...
$this->assertTrue(lock_acquire('system_test_lock_acquire'), t('Lock acquired by this request.'), t('Lock'));
$this->assertTrue(lock_acquire('system_test_lock_acquire'), t('Lock extended by this request.'), t('Lock'));
...
So two calls to lock_acquire() right next to each other. Then in the update query:
$success = (bool) db_update('semaphore')
->fields(array('expire' => $expire))
->condition('name', $name)
->condition('value', _lock_id())
->execute();
if (!$success) {
// The lock was broken.
unset($locks[$name]);
}
Sometimes $success is FALSE if the old $expire is exactly the same as the new one, because no rows are affected by the query. This only happens if the lock_acquire()'s run fast enough (in the same millisecond) but it still seems like a bug worth fixing.
Comments
Comment #1
carlos8f commentedIt probably doesn't help that I'm on a 32-bit system (apple macbook) which has trouble handling seconds + milliseconds as a float. microtime(TRUE) returns precision of only 1/100 second, instead of 1/1000 as on a 64-bit machine. With these decimated microtime values, it appears that "internally" PHP still knows there is more precision there (== comparison fails), but "externally" (when the float is cast to a string, or put into a db query) the precision is lost. Here's a perplexing test:
Output:
As you can see, precision is decimated when outputting as a string, but not when comparing these variables in an expression. Yick!
Comment #2
carlos8f commentedBefore patch lock.test failed 4/5 times on my machine, with patch I get all passes.
Comment #3
ekes commented+1 for the issue.
I've experienced this on a box running D6 when the work done is sometimes trivial between lock_acquire()'s (i.e. takes less than 0.0044. sec). I can't make it fail the _tests_ for D7 because running in simpletest is slower, but can replicate the issue outside the tests.
+1 I think for the solution.
Just a question. Adding 0.01 which is the 32-bit accuracy isn't going to cause any weird problem on a 64-bit system where it could (just could) update with a smaller value a fraction later?
My only other solution(s) were to add a select query after returning 'false' from the update and before returning saying the lock was broken; or mysql style get the rows affected not necessarily changed (but that's out of the database layer so I'm guessing not cross-platform enough).
Comment #4
ekes commentedJust the alternative way I ended up using, it's a small extra query. I'm not sure which is preferable.
Comment #5
Anonymous (not verified) commentedsubscribe.
Comment #6
Anonymous (not verified) commentedi think we should proceed with #2. still looking, but i think its the cleaner approach.
Comment #7
Anonymous (not verified) commentedfirst issue, this looks broken:
don't we want to only do that if $success == true?
Comment #8
carlos8f commentedYes, that was broken. Rerolled to fix that.
Comment #9
Anonymous (not verified) commentedok, that looks all good to me. this may be a kitten-killing suggestion, but this is a test that could use the testing profile. i'm ok if you want to leave that out, but i certainly added it when i was looking at it because it takes literally half the time to run...
will RTBC it if the bot comes back green.
Comment #10
Anonymous (not verified) commentedoh, and looking at the test, the API really needs a lock_renew(). the code is twice as hard to read without that, but i guess that's for D8.
Comment #11
xjmRegarding
microtime(), see also: