Skip to content

Commit eebbd21

Browse files
author
Eric Koleda
authored
Merge pull request #147 from gsuitedevs/locking
Reduce unnecessary usage of LockService
2 parents 3572ba7 + 1f95c32 commit eebbd21

File tree

4 files changed

+102
-23
lines changed

4 files changed

+102
-23
lines changed

src/Service.js

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -376,25 +376,37 @@ Service_.prototype.handleCallback = function(callbackRequest) {
376376
* otherwise.
377377
*/
378378
Service_.prototype.hasAccess = function() {
379+
var token = this.getToken();
380+
if (token && !this.isExpired_(token)) return true; // Token still has access.
381+
var canGetToken = (token && this.canRefresh_(token)) ||
382+
this.privateKey_ || this.grantType_;
383+
if (!canGetToken) return false;
384+
379385
return this.lockable_(function() {
380-
var token = this.getToken();
381-
if (!token || this.isExpired_(token)) {
382-
try {
383-
if (token && this.canRefresh_(token)) {
384-
this.refresh();
385-
} else if (this.privateKey_) {
386-
this.exchangeJwt_();
387-
} else if (this.grantType_) {
388-
this.exchangeGrant_();
389-
} else {
390-
return false;
391-
}
392-
} catch (e) {
393-
this.lastError_ = e;
386+
// Get the token again, bypassing the local memory cache.
387+
token = this.getToken(true);
388+
// Check to see if the token is no longer missing or expired, as another
389+
// execution may have refreshed it while we were waiting for the lock.
390+
if (token && !this.isExpired_(token)) return true; // Token now has access.
391+
try {
392+
if (token && this.canRefresh_(token)) {
393+
this.refresh();
394+
return true;
395+
} else if (this.privateKey_) {
396+
this.exchangeJwt_();
397+
return true;
398+
} else if (this.grantType_) {
399+
this.exchangeGrant_();
400+
return true;
401+
} else {
402+
// This should never happen, since canGetToken should have been false
403+
// earlier.
394404
return false;
395405
}
406+
} catch (e) {
407+
this.lastError_ = e;
408+
return false;
396409
}
397-
return true;
398410
});
399411
};
400412

@@ -579,10 +591,13 @@ Service_.prototype.saveToken_ = function(token) {
579591

580592
/**
581593
* Gets the token from the service's property store or cache.
594+
* @param {boolean?} optSkipMemoryCheck If true, bypass the local memory cache
595+
* when fetching the token.
582596
* @return {Object} The token, or null if no token was found.
583597
*/
584-
Service_.prototype.getToken = function() {
585-
return this.getStorage().getValue(null);
598+
Service_.prototype.getToken = function(optSkipMemoryCheck) {
599+
// Gets the stored value under the null key, which is reserved for the token.
600+
return this.getStorage().getValue(null, optSkipMemoryCheck);
586601
};
587602

588603
/**

src/Storage.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,23 @@ Storage_.CACHE_NULL_VALUE = '__NULL__';
5050
/**
5151
* Gets a stored value.
5252
* @param {string} key The key.
53+
* @param {boolean?} optSkipMemoryCheck Whether to bypass the local memory cache
54+
* when fetching the value (the default is false).
5355
* @return {*} The stored value.
5456
*/
55-
Storage_.prototype.getValue = function(key) {
57+
Storage_.prototype.getValue = function(key, optSkipMemoryCheck) {
5658
var prefixedKey = this.getPrefixedKey_(key);
5759
var jsonValue;
5860
var value;
5961

60-
// Check memory.
61-
if (value = this.memory_[key]) {
62-
if (value === Storage_.CACHE_NULL_VALUE) {
63-
return null;
62+
if (!optSkipMemoryCheck) {
63+
// Check in-memory cache.
64+
if (value = this.memory_[key]) {
65+
if (value === Storage_.CACHE_NULL_VALUE) {
66+
return null;
67+
}
68+
return value;
6469
}
65-
return value;
6670
}
6771

6872
// Check cache.

test/mocks/lock.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ var waitingFibers = [];
1212
var MockLock = function() {
1313
this.hasLock_ = false;
1414
this.id = Math.random();
15+
this.counter = 0;
1516
};
1617

1718
MockLock.prototype.waitLock = function(timeoutInMillis) {
@@ -20,6 +21,7 @@ MockLock.prototype.waitLock = function(timeoutInMillis) {
2021
if (!locked || this.hasLock_) {
2122
locked = true;
2223
this.hasLock_ = true;
24+
this.counter++;
2325
return;
2426
} else {
2527
waitingFibers.push(Fiber.current);

test/test.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,23 @@ describe('Service', function() {
9797
assert.equal(properties.counter, propertiesStart);
9898
});
9999

100+
it('should skip the local memory cache when desired', function() {
101+
var properties = new MockProperties();
102+
var service = OAuth2.createService('test')
103+
.setPropertyStore(properties);
104+
var token = {
105+
access_token: 'foo'
106+
};
107+
service.saveToken_(token);
108+
109+
var newToken = {
110+
access_token: 'bar'
111+
};
112+
properties.setProperty('oauth2.test', JSON.stringify(newToken));
113+
114+
assert.deepEqual(service.getToken(true), newToken);
115+
});
116+
100117
it('should load null tokens from the cache',
101118
function() {
102119
var cache = new MockCache();
@@ -212,6 +229,47 @@ describe('Service', function() {
212229
done();
213230
});
214231
});
232+
233+
it('should not acquire a lock when the token is not expired', function() {
234+
var token = {
235+
granted_time: (new Date()).getTime(),
236+
expires_in: 1000,
237+
access_token: 'foo',
238+
refresh_token: 'bar'
239+
};
240+
var lock = new MockLock();
241+
var properties = new MockProperties({
242+
'oauth2.test': JSON.stringify(token)
243+
});
244+
var service = OAuth2.createService('test')
245+
.setClientId('abc')
246+
.setClientSecret('def')
247+
.setTokenUrl('http://www.example.com')
248+
.setPropertyStore(properties)
249+
.setLock(lock);
250+
service.hasAccess();
251+
assert.equal(lock.counter, 0);
252+
});
253+
254+
it('should not acquire a lock when there is no refresh token', function() {
255+
var token = {
256+
granted_time: 100,
257+
expires_in: 100,
258+
access_token: 'foo',
259+
};
260+
var lock = new MockLock();
261+
var properties = new MockProperties({
262+
'oauth2.test': JSON.stringify(token)
263+
});
264+
var service = OAuth2.createService('test')
265+
.setClientId('abc')
266+
.setClientSecret('def')
267+
.setTokenUrl('http://www.example.com')
268+
.setPropertyStore(properties)
269+
.setLock(lock);
270+
service.hasAccess();
271+
assert.equal(lock.counter, 0);
272+
});
215273
});
216274

217275
describe('#refresh()', function() {

0 commit comments

Comments
 (0)