shard.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. // Copyright 2015 The MemDB Authors.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  12. // implied. See the License for the specific language governing
  13. // permissions and limitations under the License. See the AUTHORS file
  14. // for names of contributors.
  15. 'use strict';
  16. var P = require('bluebird');
  17. var _ = require('lodash');
  18. var should = require('should');
  19. var env = require('../env');
  20. var Shard = require('../../app/shard');
  21. var logger = require('memdb-logger').getLogger('test', __filename);
  22. describe('shard test', function(){
  23. beforeEach(env.flushdb);
  24. it('load/unload/find/update/insert/remove/commit/rollback', function(cb){
  25. var shard = new Shard(env.shardConfig('s1'));
  26. var connId = 'c1', key = 'user$1', doc = {_id : '1', name : 'rain', age : 30};
  27. return P.try(function(){
  28. return shard.start();
  29. })
  30. .then(function(){
  31. // pre create collection for performance
  32. return shard.backend.conn.createCollectionAsync('user');
  33. })
  34. .then(function(){
  35. // should auto load
  36. return shard.lock(connId, key);
  37. })
  38. .then(function(){
  39. shard._isLoaded(key).should.eql(true);
  40. // insert doc
  41. return shard.insert(connId, key, doc);
  42. })
  43. .then(function(){
  44. // request to unload doc
  45. shard.$unload(key);
  46. })
  47. .delay(20)
  48. .then(function(){
  49. // should still loaded, waiting for commit
  50. shard._isLoaded(key).should.eql(true);
  51. return shard.commit(connId, key);
  52. })
  53. .delay(100)
  54. .then(function(){
  55. // should unloaded now and saved to backend
  56. shard._isLoaded(key).should.eql(false);
  57. // load again
  58. return shard.lock(connId, key);
  59. })
  60. .then(function(){
  61. // Already loaded, should return immediately
  62. shard.find(connId, key).should.eql(doc);
  63. // load again for write
  64. return shard.lock(connId, key);
  65. })
  66. .then(function(){
  67. shard.update(connId, key, {age : 31});
  68. shard.find(connId, key, 'age').age.should.eql(31);
  69. // rollback to original value
  70. shard.rollback(connId, key);
  71. shard.find(connId, key, 'age').age.should.eql(30);
  72. return shard.lock(connId, key);
  73. })
  74. .then(function(){
  75. shard.remove(connId, key);
  76. (shard.find(connId, key) === null).should.be.true; // jshint ignore:line
  77. return shard.commit(connId, key);
  78. })
  79. .then(function(){
  80. // request to unload
  81. shard.$unload(key);
  82. })
  83. .delay(100)
  84. .then(function(){
  85. // load again
  86. return P.try(function(){
  87. return shard.lock(connId, key);
  88. })
  89. .then(function(){
  90. var doc = shard.find(connId, key);
  91. (doc === null).should.be.true; // jshint ignore:line
  92. });
  93. })
  94. .then(function(){
  95. shard.commit(connId, key);
  96. })
  97. .then(function(){
  98. return shard.stop();
  99. })
  100. .nodeify(cb);
  101. });
  102. it('backendLock between multiple shards', function(cb){
  103. var config = env.shardConfig('s1');
  104. config.backendLockRetryInterval = 500; // This is required for this test
  105. var shard1 = new Shard(config);
  106. var shard2 = new Shard(env.shardConfig('s2'));
  107. // fake autoconn
  108. shard1.autoconn.$unload = function(shardId, key){
  109. shardId.should.eql('s2');
  110. return shard2.$unload(key);
  111. };
  112. shard2.autoconn.$unload = function(shardId, key){
  113. shardId.should.eql('s1');
  114. return shard1.$unload(key);
  115. };
  116. var key = 'user$1', doc = {_id : '1', name : 'rain', age : 30};
  117. return P.try(function(){
  118. return P.all([shard1.start(), shard2.start()]);
  119. })
  120. .then(function(){
  121. // pre create collection for performance
  122. return shard1.backend.conn.createCollectionAsync('user');
  123. })
  124. .then(function(){
  125. return P.all([
  126. // shard1:c1
  127. P.try(function(){
  128. // load by shard1
  129. return shard1.lock('c1', key);
  130. })
  131. .delay(100) // wait for shard2 request
  132. .then(function(){
  133. // should unloading now (wait for unlock)
  134. // already locked, so will not block
  135. return shard1.lock('c1', key);
  136. })
  137. .then(function(){
  138. shard1.insert('c1', key, doc);
  139. return shard1.commit('c1', key);
  140. // unlocked, unloading should continue
  141. }),
  142. // shard2:c1
  143. P.delay(20) // shard1 should load first
  144. .then(function(){
  145. // This will block until shard1 unload the key
  146. return shard2.lock('c1', key);
  147. })
  148. .then(function(){
  149. var doc = shard2.find('c1', key);
  150. doc.should.eql(doc);
  151. shard2.remove('c1', key);
  152. return shard2.commit('c1', key);
  153. }),
  154. // shard1:c2
  155. P.delay(50)
  156. .then(function(){
  157. // since shard1 is unloading, lock will block until unloaded
  158. // and then will load again (after shard2 unload)
  159. return shard1.lock('c2', key);
  160. })
  161. .then(function(){
  162. // should read shard2 saved value
  163. (shard1.find('c2', key) === null).should.be.true; // jshint ignore:line
  164. })
  165. .then(function(){
  166. return shard1.commit('c2', key);
  167. })
  168. ]);
  169. })
  170. .then(function(){
  171. return P.all([shard1.stop(), shard2.stop()]);
  172. })
  173. .nodeify(cb);
  174. });
  175. it('backendLock consistency fix', function(cb){
  176. var shard1 = new Shard(env.shardConfig('s1'));
  177. var shard2 = new Shard(env.shardConfig('s2'));
  178. // fake autoconn
  179. shard1.autoconn.$unload = function(shardId, key){
  180. shardId.should.eql('s2');
  181. return shard2.$unload(key);
  182. };
  183. shard2.autoconn.$unload = function(shardId, key){
  184. shardId.should.eql('s1');
  185. return shard1.$unload(key);
  186. };
  187. var key = 'user$1', doc = {_id : '1', name : 'rain', age : 30};
  188. var errCount = 0;
  189. return P.try(function(){
  190. return P.all([shard1.start(), shard2.start()]);
  191. })
  192. .then(function(){
  193. return P.try(function(){
  194. // shard1 get a redundant lock
  195. return shard1.backendLocker.tryLock(key);
  196. })
  197. .then(function(){
  198. // should also access
  199. return shard2.lock('c1', key);
  200. });
  201. })
  202. .then(function(){
  203. return shard2.commit('c1', key);
  204. })
  205. .then(function(){
  206. return P.all([shard1.stop(), shard2.stop()]);
  207. })
  208. .nodeify(cb);
  209. });
  210. });