├── .gitignore ├── CHANGES.md ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.md ├── api.md ├── docs.json ├── index.js ├── lib ├── collection.js ├── lib.js ├── master │ ├── Client.js │ ├── Collection.js │ ├── Entry.js │ ├── KeyLock.js │ └── setup.js └── worker │ ├── Collection.js │ ├── KeyLock.js │ └── request.js ├── package.json └── test ├── concurrent-inc.js ├── get-set-del.js ├── helper ├── cluster-helper.js ├── do-concurrent-inc.js ├── do-get-set-del.js └── do-lock-get-set-del.js └── lock-get-set-del.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | 2016-05-03, Version 1.0.1 2 | ========================= 3 | 4 | * insert/update copyright notices (Ryan Graham) 5 | 6 | * relicense as Artistic-2.0 only (Ryan Graham) 7 | 8 | * Refer to licenses with a link (Sam Roberts) 9 | 10 | * Use strongloop conventions for licensing (Sam Roberts) 11 | 12 | 13 | 2015-03-23, Version 1.0.0 14 | ========================= 15 | 16 | * package: correct license link (Sam Roberts) 17 | 18 | * Update a documentation link (Yohei Yamaguchi) 19 | 20 | * Fix bad CLA URL in CONTRIBUTING.md (Ryan Graham) 21 | 22 | * Update contribution guidelines (Ryan Graham) 23 | 24 | * Update package license to match LICENSE.md (Sam Roberts) 25 | 26 | * gitignore node_modules folder (Sam Roberts) 27 | 28 | * doc: add CONTRIBUTING.md and LICENSE.md (Ben Noordhuis) 29 | 30 | * Apply Dual MIT/StrongLoop license (Sam Roberts) 31 | 32 | * Replace old README with link to docs. (Rand McKinney) 33 | 34 | * Update docs.json (Rand McKinney) 35 | 36 | * Update api.md (Rand McKinney) 37 | 38 | * Update README.md (Rand McKinney) 39 | 40 | * Create api.md (Rand McKinney) 41 | 42 | * remove unused blanket dependencies (Sam Roberts) 43 | 44 | 45 | 2013-10-29, Version 0.1.3 46 | ========================= 47 | 48 | * Detect and fail on invalid multiple instantiation (Sam Roberts) 49 | 50 | * Minor doc improvements, added example and docs.json. (Edmond Meinfelder) 51 | 52 | * add blanket.js (slnode) 53 | 54 | 55 | 2013-07-15, Version 0.1.2 56 | ========================= 57 | 58 | * Now working on v0.1.2 (Bert Belder) 59 | 60 | 61 | 2013-07-15, Version 0.1.1 62 | ========================= 63 | 64 | * test: add makefile with test targets (Bert Belder) 65 | 66 | * test: make `npm test` work (Bert Belder) 67 | 68 | * test: add some tests (Bert Belder) 69 | 70 | * package.json: reorganize (Bert Belder) 71 | 72 | * Entry (master): use nextTick selectively (Bert Belder) 73 | 74 | * KeyLock (worker): don't JSON.stringify twice (Bert Belder) 75 | 76 | * Entry (master): add missing function `noop()` (Bert Belder) 77 | 78 | * Entry (master): use setImmediate and not nextTick (Bert Belder) 79 | 80 | * started work on v0.1.1 (Miroslav Bajtos) 81 | 82 | 83 | 2013-07-12, Version 0.1.0 84 | ========================= 85 | 86 | * First release! 87 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Contributing ### 2 | 3 | Thank you for your interest in `strong-store-cluster`, an open source project 4 | administered by StrongLoop. 5 | 6 | Contributing to `strong-store-cluster` is easy. In a few simple steps: 7 | 8 | * Ensure that your effort is aligned with the project's roadmap by 9 | talking to the maintainers, especially if you are going to spend a 10 | lot of time on it. 11 | 12 | * Make something better or fix a bug. 13 | 14 | * Adhere to code style outlined in the [Google C++ Style Guide][] and 15 | [Google Javascript Style Guide][]. 16 | 17 | * Sign the [Contributor License Agreement](https://cla.strongloop.com/agreements/strongloop/strong-store-cluster) 18 | 19 | * Submit a pull request through Github. 20 | 21 | 22 | ### Contributor License Agreement ### 23 | 24 | ``` 25 | Individual Contributor License Agreement 26 | 27 | By signing this Individual Contributor License Agreement 28 | ("Agreement"), and making a Contribution (as defined below) to 29 | StrongLoop, Inc. ("StrongLoop"), You (as defined below) accept and 30 | agree to the following terms and conditions for Your present and 31 | future Contributions submitted to StrongLoop. Except for the license 32 | granted in this Agreement to StrongLoop and recipients of software 33 | distributed by StrongLoop, You reserve all right, title, and interest 34 | in and to Your Contributions. 35 | 36 | 1. Definitions 37 | 38 | "You" or "Your" shall mean the copyright owner or the individual 39 | authorized by the copyright owner that is entering into this 40 | Agreement with StrongLoop. 41 | 42 | "Contribution" shall mean any original work of authorship, 43 | including any modifications or additions to an existing work, that 44 | is intentionally submitted by You to StrongLoop for inclusion in, 45 | or documentation of, any of the products owned or managed by 46 | StrongLoop ("Work"). For purposes of this definition, "submitted" 47 | means any form of electronic, verbal, or written communication 48 | sent to StrongLoop or its representatives, including but not 49 | limited to communication or electronic mailing lists, source code 50 | control systems, and issue tracking systems that are managed by, 51 | or on behalf of, StrongLoop for the purpose of discussing and 52 | improving the Work, but excluding communication that is 53 | conspicuously marked or otherwise designated in writing by You as 54 | "Not a Contribution." 55 | 56 | 2. You Grant a Copyright License to StrongLoop 57 | 58 | Subject to the terms and conditions of this Agreement, You hereby 59 | grant to StrongLoop and recipients of software distributed by 60 | StrongLoop, a perpetual, worldwide, non-exclusive, no-charge, 61 | royalty-free, irrevocable copyright license to reproduce, prepare 62 | derivative works of, publicly display, publicly perform, 63 | sublicense, and distribute Your Contributions and such derivative 64 | works under any license and without any restrictions. 65 | 66 | 3. You Grant a Patent License to StrongLoop 67 | 68 | Subject to the terms and conditions of this Agreement, You hereby 69 | grant to StrongLoop and to recipients of software distributed by 70 | StrongLoop a perpetual, worldwide, non-exclusive, no-charge, 71 | royalty-free, irrevocable (except as stated in this Section) 72 | patent license to make, have made, use, offer to sell, sell, 73 | import, and otherwise transfer the Work under any license and 74 | without any restrictions. The patent license You grant to 75 | StrongLoop under this Section applies only to those patent claims 76 | licensable by You that are necessarily infringed by Your 77 | Contributions(s) alone or by combination of Your Contributions(s) 78 | with the Work to which such Contribution(s) was submitted. If any 79 | entity institutes a patent litigation against You or any other 80 | entity (including a cross-claim or counterclaim in a lawsuit) 81 | alleging that Your Contribution, or the Work to which You have 82 | contributed, constitutes direct or contributory patent 83 | infringement, any patent licenses granted to that entity under 84 | this Agreement for that Contribution or Work shall terminate as 85 | of the date such litigation is filed. 86 | 87 | 4. You Have the Right to Grant Licenses to StrongLoop 88 | 89 | You represent that You are legally entitled to grant the licenses 90 | in this Agreement. 91 | 92 | If Your employer(s) has rights to intellectual property that You 93 | create, You represent that You have received permission to make 94 | the Contributions on behalf of that employer, that Your employer 95 | has waived such rights for Your Contributions, or that Your 96 | employer has executed a separate Corporate Contributor License 97 | Agreement with StrongLoop. 98 | 99 | 5. The Contributions Are Your Original Work 100 | 101 | You represent that each of Your Contributions are Your original 102 | works of authorship (see Section 8 (Submissions on Behalf of 103 | Others) for submission on behalf of others). You represent that to 104 | Your knowledge, no other person claims, or has the right to claim, 105 | any right in any intellectual property right related to Your 106 | Contributions. 107 | 108 | You also represent that You are not legally obligated, whether by 109 | entering into an agreement or otherwise, in any way that conflicts 110 | with the terms of this Agreement. 111 | 112 | You represent that Your Contribution submissions include complete 113 | details of any third-party license or other restriction (including, 114 | but not limited to, related patents and trademarks) of which You 115 | are personally aware and which are associated with any part of 116 | Your Contributions. 117 | 118 | 6. You Don't Have an Obligation to Provide Support for Your Contributions 119 | 120 | You are not expected to provide support for Your Contributions, 121 | except to the extent You desire to provide support. You may provide 122 | support for free, for a fee, or not at all. 123 | 124 | 6. No Warranties or Conditions 125 | 126 | StrongLoop acknowledges that unless required by applicable law or 127 | agreed to in writing, You provide Your Contributions on an "AS IS" 128 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 129 | EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES 130 | OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR 131 | FITNESS FOR A PARTICULAR PURPOSE. 132 | 133 | 7. Submission on Behalf of Others 134 | 135 | If You wish to submit work that is not Your original creation, You 136 | may submit it to StrongLoop separately from any Contribution, 137 | identifying the complete details of its source and of any license 138 | or other restriction (including, but not limited to, related 139 | patents, trademarks, and license agreements) of which You are 140 | personally aware, and conspicuously marking the work as 141 | "Submitted on Behalf of a Third-Party: [named here]". 142 | 143 | 8. Agree to Notify of Change of Circumstances 144 | 145 | You agree to notify StrongLoop of any facts or circumstances of 146 | which You become aware that would make these representations 147 | inaccurate in any respect. Email us at callback@strongloop.com. 148 | ``` 149 | 150 | [Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html 151 | [Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml 152 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) IBM Corp. 2013,2015. All Rights Reserved. 2 | Node module: strong-store-cluster 3 | This project is licensed under the Artistic License 2.0, full text below. 4 | 5 | -------- 6 | 7 | The Artistic License 2.0 8 | 9 | Copyright (c) 2000-2006, The Perl Foundation. 10 | 11 | Everyone is permitted to copy and distribute verbatim copies 12 | of this license document, but changing it is not allowed. 13 | 14 | Preamble 15 | 16 | This license establishes the terms under which a given free software 17 | Package may be copied, modified, distributed, and/or redistributed. 18 | The intent is that the Copyright Holder maintains some artistic 19 | control over the development of that Package while still keeping the 20 | Package available as open source and free software. 21 | 22 | You are always permitted to make arrangements wholly outside of this 23 | license directly with the Copyright Holder of a given Package. If the 24 | terms of this license do not permit the full use that you propose to 25 | make of the Package, you should contact the Copyright Holder and seek 26 | a different licensing arrangement. 27 | 28 | Definitions 29 | 30 | "Copyright Holder" means the individual(s) or organization(s) 31 | named in the copyright notice for the entire Package. 32 | 33 | "Contributor" means any party that has contributed code or other 34 | material to the Package, in accordance with the Copyright Holder's 35 | procedures. 36 | 37 | "You" and "your" means any person who would like to copy, 38 | distribute, or modify the Package. 39 | 40 | "Package" means the collection of files distributed by the 41 | Copyright Holder, and derivatives of that collection and/or of 42 | those files. A given Package may consist of either the Standard 43 | Version, or a Modified Version. 44 | 45 | "Distribute" means providing a copy of the Package or making it 46 | accessible to anyone else, or in the case of a company or 47 | organization, to others outside of your company or organization. 48 | 49 | "Distributor Fee" means any fee that you charge for Distributing 50 | this Package or providing support for this Package to another 51 | party. It does not mean licensing fees. 52 | 53 | "Standard Version" refers to the Package if it has not been 54 | modified, or has been modified only in ways explicitly requested 55 | by the Copyright Holder. 56 | 57 | "Modified Version" means the Package, if it has been changed, and 58 | such changes were not explicitly requested by the Copyright 59 | Holder. 60 | 61 | "Original License" means this Artistic License as Distributed with 62 | the Standard Version of the Package, in its current version or as 63 | it may be modified by The Perl Foundation in the future. 64 | 65 | "Source" form means the source code, documentation source, and 66 | configuration files for the Package. 67 | 68 | "Compiled" form means the compiled bytecode, object code, binary, 69 | or any other form resulting from mechanical transformation or 70 | translation of the Source form. 71 | 72 | 73 | Permission for Use and Modification Without Distribution 74 | 75 | (1) You are permitted to use the Standard Version and create and use 76 | Modified Versions for any purpose without restriction, provided that 77 | you do not Distribute the Modified Version. 78 | 79 | 80 | Permissions for Redistribution of the Standard Version 81 | 82 | (2) You may Distribute verbatim copies of the Source form of the 83 | Standard Version of this Package in any medium without restriction, 84 | either gratis or for a Distributor Fee, provided that you duplicate 85 | all of the original copyright notices and associated disclaimers. At 86 | your discretion, such verbatim copies may or may not include a 87 | Compiled form of the Package. 88 | 89 | (3) You may apply any bug fixes, portability changes, and other 90 | modifications made available from the Copyright Holder. The resulting 91 | Package will still be considered the Standard Version, and as such 92 | will be subject to the Original License. 93 | 94 | 95 | Distribution of Modified Versions of the Package as Source 96 | 97 | (4) You may Distribute your Modified Version as Source (either gratis 98 | or for a Distributor Fee, and with or without a Compiled form of the 99 | Modified Version) provided that you clearly document how it differs 100 | from the Standard Version, including, but not limited to, documenting 101 | any non-standard features, executables, or modules, and provided that 102 | you do at least ONE of the following: 103 | 104 | (a) make the Modified Version available to the Copyright Holder 105 | of the Standard Version, under the Original License, so that the 106 | Copyright Holder may include your modifications in the Standard 107 | Version. 108 | 109 | (b) ensure that installation of your Modified Version does not 110 | prevent the user installing or running the Standard Version. In 111 | addition, the Modified Version must bear a name that is different 112 | from the name of the Standard Version. 113 | 114 | (c) allow anyone who receives a copy of the Modified Version to 115 | make the Source form of the Modified Version available to others 116 | under 117 | 118 | (i) the Original License or 119 | 120 | (ii) a license that permits the licensee to freely copy, 121 | modify and redistribute the Modified Version using the same 122 | licensing terms that apply to the copy that the licensee 123 | received, and requires that the Source form of the Modified 124 | Version, and of any works derived from it, be made freely 125 | available in that license fees are prohibited but Distributor 126 | Fees are allowed. 127 | 128 | 129 | Distribution of Compiled Forms of the Standard Version 130 | or Modified Versions without the Source 131 | 132 | (5) You may Distribute Compiled forms of the Standard Version without 133 | the Source, provided that you include complete instructions on how to 134 | get the Source of the Standard Version. Such instructions must be 135 | valid at the time of your distribution. If these instructions, at any 136 | time while you are carrying out such distribution, become invalid, you 137 | must provide new instructions on demand or cease further distribution. 138 | If you provide valid instructions or cease distribution within thirty 139 | days after you become aware that the instructions are invalid, then 140 | you do not forfeit any of your rights under this license. 141 | 142 | (6) You may Distribute a Modified Version in Compiled form without 143 | the Source, provided that you comply with Section 4 with respect to 144 | the Source of the Modified Version. 145 | 146 | 147 | Aggregating or Linking the Package 148 | 149 | (7) You may aggregate the Package (either the Standard Version or 150 | Modified Version) with other packages and Distribute the resulting 151 | aggregation provided that you do not charge a licensing fee for the 152 | Package. Distributor Fees are permitted, and licensing fees for other 153 | components in the aggregation are permitted. The terms of this license 154 | apply to the use and Distribution of the Standard or Modified Versions 155 | as included in the aggregation. 156 | 157 | (8) You are permitted to link Modified and Standard Versions with 158 | other works, to embed the Package in a larger work of your own, or to 159 | build stand-alone binary or bytecode versions of applications that 160 | include the Package, and Distribute the result without restriction, 161 | provided the result does not expose a direct interface to the Package. 162 | 163 | 164 | Items That are Not Considered Part of a Modified Version 165 | 166 | (9) Works (including, but not limited to, modules and scripts) that 167 | merely extend or make use of the Package, do not, by themselves, cause 168 | the Package to be a Modified Version. In addition, such works are not 169 | considered parts of the Package itself, and are not subject to the 170 | terms of this license. 171 | 172 | 173 | General Provisions 174 | 175 | (10) Any use, modification, and distribution of the Standard or 176 | Modified Versions is governed by this Artistic License. By using, 177 | modifying or distributing the Package, you accept this license. Do not 178 | use, modify, or distribute the Package, if you do not accept this 179 | license. 180 | 181 | (11) If your Modified Version has been derived from a Modified 182 | Version made by someone other than you, you are nevertheless required 183 | to ensure that your Modified Version complies with the requirements of 184 | this license. 185 | 186 | (12) This license does not grant you the right to use any trademark, 187 | service mark, tradename, or logo of the Copyright Holder. 188 | 189 | (13) This license includes the non-exclusive, worldwide, 190 | free-of-charge patent license to make, have made, use, offer to sell, 191 | sell, import and otherwise transfer the Package with respect to any 192 | patent claims licensable by the Copyright Holder that are necessarily 193 | infringed by the Package. If you institute patent litigation 194 | (including a cross-claim or counterclaim) against any party alleging 195 | that the Package constitutes direct or contributory patent 196 | infringement, then this Artistic License to you shall terminate on the 197 | date that such litigation is filed. 198 | 199 | (14) Disclaimer of Warranty: 200 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS 201 | IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED 202 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 203 | NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL 204 | LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL 205 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 206 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF 207 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 208 | 209 | 210 | -------- 211 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | -include local.mk 4 | 5 | .PHONY: test default 6 | 7 | default: test 8 | 9 | test: 10 | @npm test 11 | 12 | jenkins-build: jenkins-install jenkins-test 13 | 14 | jenkins-install: 15 | npm install 16 | 17 | jenkins-test: 18 | ./node_modules/.bin/mocha --timeout 5000 --slow 1000 --ui tdd --reporter xunit > xunit.xml 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Strong Store Cluster 2 | 3 | Strong Store for Cluster provides a key/value collection that can be accesses by 4 | all processes in a node cluster. 5 | 6 | Please see the [official documentation](http://docs.strongloop.com/display/SLC/Strong+Store+for+Cluster). 7 | 8 | For API documentation, see [strong-store-cluster API reference](http://apidocs.strongloop.com/strong-store-cluster/). 9 | -------------------------------------------------------------------------------- /api.md: -------------------------------------------------------------------------------- 1 | ## store.collection(name) 2 | 3 | Returns a Collection object which lets you share data between node processes. 4 | 5 | ## Collection class 6 | 7 | A `Collection` instance provides access to a shared key-value store 8 | shared by multiple node instances. 9 | 10 | How collections are named and stored is determined by the storage backend. The 11 | `strong-store-cluster` implementation stores collections in the master process 12 | (if you're using cluster), and accepts any arbitrary string as a collection 13 | name. 14 | 15 | A `Collection` object is also an `EventEmitter`. 16 | 17 | ### Methods 18 | 19 | #### collection.configure([options]) 20 | 21 | * `options` (`Object`) contains configurations options to be changed 22 | * `expireKeys` (`Number`) seconds after which keys in this 23 | collection are to be expired. 24 | 25 | Set configuration options for the collection. 26 | 27 | Currently only one configurable option is supported: `expireKeys`. When set 28 | to a nonzero value, keys will automatically expire after they've not been 29 | read or updated for some time. The timeout is specified in seconds. There's no 30 | guarantee that the key will be discared after exactly that number of seconds 31 | has passed. However keys will never be automatically deleted _sooner_ than what 32 | the `expireKeys` setting allows. 33 | 34 | It is perfectly legal to call the `configure` method from multiple node 35 | processes (e.g. both in a worker and in the master process). However you 36 | should be careful to set the _same_ option values every time, otherwise the 37 | effect is undefined. 38 | 39 | #### collection.get(key, callback) 40 | 41 | * `key` (`String`) key to retrieve 42 | * `callback` (`Function`) called when the value has been retrieved 43 | 44 | Read the value associated with a particular key. The callback is called with 45 | two arguments, `(err, value)`. When the key wasn't found in the collection, it 46 | is automatically created and it's `value` is set to `undefined`. 47 | 48 | #### collection.set(key, [value], [callback]) 49 | 50 | * `key` (`String`) key to set or update 51 | * `value` (`object`) value to associate with the key 52 | * `callback` (`Function`) called when the value has been retrieved 53 | 54 | Set the value associated with `key`. The `value` must be either undefined or 55 | a value that can be serialized with JSON.stringify. 56 | 57 | When the `value` parameter is omitted or set to `undefined`, the key is 58 | deleted, so effectively it's the same as calling `collection.del(key)`. 59 | 60 | The `callback` function receives only one argument, `err`. When the 61 | callback is omitted, the master process does not send a confirmation 62 | after updating the key, and any errors are silently ignored. 63 | 64 | #### collection.del(key, [callback]) 65 | 66 | * `key` (`String`) key to delete 67 | * `callback` (`Function`) called when the value has been retrieved 68 | 69 | Delete a key from the collection. 70 | 71 | This operation is the equivalent of setting the key to `undefined`. 72 | 73 | The `callback` function receives only one argument, `err`. When the 74 | callback is omitted, the master process does not send a confirmation 75 | after deleting the key, and any errors are silently ignored. 76 | 77 | #### collection.acquire(key, callback) 78 | 79 | * `key` (`String`) key to delete 80 | * `callback` (`Function`) called when the key has been locked 81 | 82 | Lock a key for exclusive read and write access. 83 | 84 | The `acquire` methods waits until it can grab an exclusive lock on the 85 | specified key. When the lock is acquired, no other process can read, write or 86 | delete this particular key. When the lock is no longer needed, it should be 87 | relinquished with `keylock.release()`. 88 | 89 | Three parameters are passed to the `callback` function: 90 | `(err, keylock, value)`. The `keylock` argument receives a `KeyLock` class 91 | instance, which lets you read and manipulate the key's value as well as 92 | eventually release the lock. The `value` argument is set to the initial value 93 | associated with the key. 94 | 95 | ### Events 96 | 97 | #### 'error' 98 | 99 | * `err` (`Error`) 100 | 101 | The error event is emitted whenever an unrecoverable error is encountered. 102 | 103 | ## KeyLock class 104 | 105 | A `KeyLock` instance represents a key that has been locked. The `KeyLock` 106 | class implements methods that lets you manipulate the key and release 107 | the lock. 108 | 109 | ### Methods 110 | 111 | #### keylock.get() 112 | 113 | * Returns: (`Object`) value that's currently associated with the key 114 | 115 | This function returns the value that's currently associated with the locked 116 | key. 117 | 118 | Initially this is the same as the `value` argument that was passed to the 119 | `collection.acquire()` callback, but it does immediately reflect changes that 120 | are made with `keylock.set()` and `keylock.del()`. 121 | 122 | #### keylock.set([value]) 123 | 124 | Updates the value associated with a locked key. 125 | 126 | The change isn't pushed back to the master process immediately; the change 127 | is committed when the lock is released again. The change however is reflected 128 | immediately in the return value from `keylock.get()`. 129 | 130 | After the lock has been released, the key can no longer be updated through the 131 | `KeyLock` instance. Any attempt to do so will make it throw. 132 | 133 | Setting the value to `undefined` marks the key for deletion, e.g. it's 134 | equivalent to `keylock.del()`. 135 | 136 | #### keylock.del() 137 | 138 | Mark a locked key for deletion. See `keylock.set()`. 139 | 140 | #### keylock.release([callback]) 141 | 142 | Release the lock that protects a key. If the key was updated with 143 | `keylock.set()` or `keylock.del()`, these changes are committed. 144 | 145 | When a lock has been released, it is no longer possible to manipulate the 146 | key using `KeyLock` methods. Releasing the lock twice isn't allowed either. 147 | The `get()` method will still work but it won't reflect any value changes 148 | that were made after releasing. 149 | 150 | The `callback` function receives only one argument, `err`. When the 151 | callback is omitted, the master process does not send a confirmation 152 | after releasing the key, and any errors are silently ignored. 153 | -------------------------------------------------------------------------------- /docs.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Strong Store for Cluster", 3 | "content": [ 4 | "api.md" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | var assert = require('assert'); 7 | var cluster = require('cluster'); 8 | var VERSION = require('./package.json').version; 9 | 10 | if(cluster._strongStoreCluster) { 11 | assert( 12 | cluster._strongStoreCluster.VERSION === VERSION, 13 | 'Multiple versions of strong-strore-cluster are being initialized.\n' + 14 | 'This version ' + VERSION + ' is incompatible with already initialized\n' + 15 | 'version ' + cluster._strongStoreCluster.VERSION + '.\n' 16 | ); 17 | module.exports = cluster._strongStoreCluster; 18 | return; 19 | } 20 | 21 | module.exports = require('./lib/lib.js'); 22 | module.exports.VERSION = VERSION; 23 | cluster._strongStoreCluster = module.exports; 24 | -------------------------------------------------------------------------------- /lib/collection.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | module.exports = collection; 7 | 8 | 9 | var hasOwnProperty = Object.prototype.hasOwnProperty; 10 | 11 | var cluster = require('cluster'); 12 | 13 | if (cluster.isMaster) 14 | var Collection = require('./master/Collection.js'); 15 | else 16 | var Collection = require('./worker/Collection.js'); 17 | 18 | 19 | var collections = {}; 20 | 21 | function collection(name) { 22 | if (!hasOwnProperty.call(collections, name)) 23 | return collections[name] = new Collection(name); 24 | else 25 | return collections[name]; 26 | } 27 | -------------------------------------------------------------------------------- /lib/lib.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | var cluster = require('cluster'), 7 | collection = require('./collection.js'); 8 | 9 | exports.collection = collection; 10 | 11 | if (cluster.isMaster) 12 | require('./master/setup.js'); 13 | -------------------------------------------------------------------------------- /lib/master/Client.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | module.exports = Client; 7 | 8 | 9 | var collection = require('../collection.js'); 10 | 11 | 12 | function Client(worker) { 13 | this._worker = worker; 14 | this._id = worker.id; 15 | this._locks = {}; 16 | 17 | this._onMessage = this.onMessage.bind(this); 18 | 19 | worker.on('message', this._onMessage); 20 | } 21 | 22 | Client.prototype.onMessage = function(msg) { 23 | if (msg.type === 'DSM_REQUEST') 24 | this[msg.method](msg); 25 | }; 26 | 27 | Client.prototype.get = function(msg) { 28 | var self = this, 29 | entry = collection(msg.collection)._entry(msg.key); 30 | 31 | entry.get(this._id, function(err, json) { 32 | if (err) self._sendError(msg, err); 33 | else self._sendReply(msg, { json: json }); 34 | }); 35 | }; 36 | 37 | Client.prototype.set = function(msg) { 38 | var self = this, 39 | entry = collection(msg.collection)._entry(msg.key); 40 | 41 | entry.set(msg.json, this._id, function(err) { 42 | if (err) self._sendError(msg, err); 43 | else self._sendReply(msg); 44 | }); 45 | }; 46 | 47 | Client.prototype.acquire = function(msg) { 48 | var self = this, 49 | entry = collection(msg.collection)._entry(msg.key); 50 | 51 | entry.acquire(this._id, function(err, json) { 52 | if (err) self._sendError(msg, err); 53 | else self._sendReply(msg, { json: json }); 54 | }); 55 | }; 56 | 57 | Client.prototype.release = function(msg) { 58 | var self = this, 59 | entry = collection(msg.collection)._entry(msg.key); 60 | 61 | entry.release(this._id, function(err, json) { 62 | if (err) self._sendError(msg, err); 63 | else self._sendReply(msg); 64 | }); 65 | }; 66 | 67 | Client.prototype.setRelease = function(msg) { 68 | var self = this, 69 | entry = collection(msg.collection)._entry(msg.key); 70 | 71 | entry.setRelease(msg.json, this._id, function(err) { 72 | if (err) self._sendError(msg, err); 73 | else self._sendReply(msg); 74 | }); 75 | }; 76 | 77 | Client.prototype.configure = function(msg) { 78 | var coll = collection(msg.collection), 79 | err = undefined; 80 | 81 | try { 82 | coll._applyConfig(msg.config); 83 | this._sendReply(msg); 84 | } catch (err) { 85 | this._sendError(msg, err); 86 | } 87 | }; 88 | 89 | // This function clobbers the data argument if specified! 90 | Client.prototype._sendReply = function(msg, data) { 91 | if (!msg.requestId) 92 | return; 93 | 94 | data = data || {}; 95 | data.type = 'DSM_REPLY'; 96 | data.requestId = msg.requestId; 97 | data.err = undefined; 98 | this._worker.send(data); 99 | }; 100 | 101 | Client.prototype._sendError = function(msg, err) { 102 | if (!msg.requestId) 103 | return; 104 | 105 | var data = {}; 106 | data.type = 'DSM_REPLY'; 107 | data.requestId = msg.requestId; 108 | data.err = err; 109 | this._worker.send(data); 110 | }; 111 | -------------------------------------------------------------------------------- /lib/master/Collection.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | module.exports = Collection; 7 | 8 | 9 | var hasOwnProperty = Object.prototype.hasOwnProperty; 10 | 11 | var assert = require('assert'), 12 | inherits = require('util').inherits, 13 | EventEmitter = require('events').EventEmitter; 14 | 15 | var Entry = require('./Entry.js'), 16 | KeyLock = require('./KeyLock.js'); 17 | 18 | 19 | function Collection(name) { 20 | this._name = name; 21 | this._entries = {}; 22 | this._count = 0; 23 | this._expireKeys = null; 24 | this._expireKeysTimer = null; 25 | 26 | this._sweep = this._sweep.bind(this); 27 | } 28 | 29 | inherits(Collection, EventEmitter); 30 | 31 | Collection.prototype._entry = function(key) { 32 | if (!hasOwnProperty.call(this._entries, key)) { 33 | if (!this._count++ && this._expireKeys) 34 | this._startExpireKeysTimer(); 35 | 36 | return this._entries[key] = new Entry(this, key); 37 | } else { 38 | return this._entries[key]; 39 | } 40 | }; 41 | 42 | Collection.prototype._remove = function(key) { 43 | assert(hasOwnProperty.call(this._entries, key)); 44 | delete this._entries[key]; 45 | 46 | if (!--this._count && this.expireKeys) 47 | this._stopExpireKeysTimer(); 48 | }; 49 | 50 | Collection.prototype.get = function(key, cb) { 51 | this._entry(key).get(-1, function(err, json) { 52 | if (err) 53 | cb(err); 54 | else if (!json) 55 | cb(null, undefined); 56 | else 57 | cb(null, JSON.parse(json)); 58 | }); 59 | }; 60 | 61 | Collection.prototype.set = function(key, value, cb) { 62 | cb = cb || noop; 63 | this._entry(key).set(JSON.stringify(value), -1, cb); 64 | }; 65 | 66 | Collection.prototype.del = function(key, cb) { 67 | cb = cb || noop; 68 | this._entry(key).set(undefined, -1, cb); 69 | }; 70 | 71 | Collection.prototype.acquire = function(key, cb) { 72 | var self = this, 73 | entry = this._entry(key); 74 | 75 | entry.acquire(-1, function(err, json) { 76 | var lock = new KeyLock(entry, json); 77 | return cb(null, lock, lock.get()); 78 | }); 79 | }; 80 | 81 | Collection.prototype._applyConfig = function(config) { 82 | config = config || {}; 83 | 84 | for (var key in config) { 85 | if (!hasOwnProperty.call(config, key)) 86 | continue; 87 | 88 | switch (key) { 89 | case 'expireKeys': 90 | if (config.expireKeys === this._expireKeys) 91 | break; 92 | 93 | this._stopExpireKeysTimer(); 94 | this._expireKeys = config.expireKeys; 95 | this._startExpireKeysTimer(); 96 | break; 97 | 98 | default: 99 | throw new Error('Unspported configuration option: ' + key); 100 | } 101 | } 102 | }; 103 | 104 | Collection.prototype._startExpireKeysTimer = function() { 105 | assert(!this._expireKeysTimer); 106 | 107 | if (!this._expireKeys || !this._count) 108 | return; 109 | 110 | var interval = Math.ceil(this._expireKeys * 1000 / 2); 111 | 112 | this._expireKeysTimer = setInterval(this._sweep, interval); 113 | this._expireKeysTimer.unref(); 114 | }; 115 | 116 | Collection.prototype._stopExpireKeysTimer = function() { 117 | if (!this._expireKeysTimer) 118 | return; 119 | 120 | clearInterval(this._expireKeysTimer); 121 | this._expireKeysTimer = null; 122 | }; 123 | 124 | Collection.prototype._sweep = function() { 125 | var entries = this._entries, 126 | key; 127 | 128 | for (key in entries) { 129 | if (!hasOwnProperty.call(entries, key)) 130 | continue; 131 | 132 | if (entries[key].age(1) > 2) 133 | this._remove(key); 134 | } 135 | 136 | if (!this._count) 137 | this._stopExpireKeysTimer(); 138 | }; 139 | 140 | Collection.prototype.configure = function(config) { 141 | var self = this; 142 | 143 | try { 144 | this._applyConfig(config); 145 | 146 | } catch (err) { 147 | process.nextTick(function() { 148 | self.emit('error', err); 149 | }); 150 | } 151 | 152 | return this; 153 | }; 154 | 155 | 156 | function noop() { 157 | } 158 | -------------------------------------------------------------------------------- /lib/master/Entry.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | module.exports = Entry; 7 | 8 | 9 | var assert = require('assert'); 10 | 11 | 12 | function Entry(collection, key) { 13 | this._collection = collection; 14 | this._key = key; 15 | this._value = undefined; 16 | this._queue = []; 17 | this._age = 0; 18 | } 19 | 20 | Entry.prototype.get = function(requestor, cb) { 21 | var self = this; 22 | 23 | if (!this._queue.length) { 24 | var value = self._value; 25 | self._age = 0; 26 | 27 | process.nextTick(function() { 28 | cb(null, value); 29 | }); 30 | 31 | } else { 32 | this.acquire(requestor, function(err) { 33 | self.release(requestor, noop); 34 | cb(null, self._value); 35 | }); 36 | } 37 | }; 38 | 39 | Entry.prototype.set = function(newValue, requestor, cb) { 40 | var self = this; 41 | 42 | if (!this._queue.length) { 43 | this._value = newValue; 44 | this._age = 0; 45 | 46 | if (newValue === undefined) 47 | this._collection._remove(this._key); 48 | 49 | process.nextTick(cb); 50 | 51 | } else { 52 | this.acquire(requestor, function() { 53 | self._value = newValue; 54 | 55 | if (newValue !== undefined) 56 | self.release(requestor, cb); 57 | else { 58 | self._collection._remove(self._key); 59 | cb(); 60 | } 61 | }); 62 | } 63 | }; 64 | 65 | Entry.prototype.acquire = function(requestor, cb) { 66 | var self = this; 67 | 68 | this._queue.push([cb, requestor]); 69 | 70 | if (this._queue.length === 1) { 71 | process.nextTick(function() { 72 | cb(null, self._value); 73 | }); 74 | } 75 | }; 76 | 77 | Entry.prototype.release = function(requestor, cb) { 78 | var self = this, 79 | queue = this._queue; 80 | 81 | setImmediate(function() { 82 | assert.strictEqual(requestor, queue.shift()[1]); 83 | 84 | self._age = 0; 85 | cb(); 86 | 87 | if (queue.length) 88 | queue[0][0](null, self._value); 89 | }); 90 | }; 91 | 92 | Entry.prototype.setRelease = function(newValue, requestor, cb) { 93 | this._value = newValue; 94 | this.release(requestor, cb); 95 | }; 96 | 97 | 98 | Entry.prototype.age = function(d) { 99 | if (this._queue.length) 100 | return this._age = 0; 101 | else 102 | return this._age += (d || 0); 103 | }; 104 | 105 | 106 | function noop() { 107 | } 108 | -------------------------------------------------------------------------------- /lib/master/KeyLock.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | module.exports = KeyLock; 7 | 8 | 9 | function KeyLock(entry, json) { 10 | this._entry = entry; 11 | this._json = json; 12 | this._updated = false; 13 | this._released = false; 14 | } 15 | 16 | KeyLock.prototype.get = function() { 17 | if (!this._json) 18 | return undefined; 19 | else 20 | return JSON.parse(this._json); 21 | }; 22 | 23 | KeyLock.prototype.set = function(newValue) { 24 | if (this._released) 25 | throw new Error("Can't set after releasing a lock."); 26 | 27 | this._json = JSON.stringify(newValue); 28 | this._updated = true; 29 | }; 30 | 31 | KeyLock.prototype.del = function() { 32 | if (this._released) 33 | throw new Error("Can't delete after releasing a lock."); 34 | 35 | this._json = undefined; 36 | this._updated = true; 37 | }; 38 | 39 | KeyLock.prototype.release = function(cb) { 40 | if (this._released) 41 | throw new Error('KeyLock has already been released.'); 42 | 43 | cb = cb || noop; 44 | this._released = true; 45 | 46 | if (!this._updated) 47 | this._entry.release(-1, cb); 48 | else 49 | this._entry.setRelease(this._json, -1, cb); 50 | }; 51 | 52 | 53 | function noop() { 54 | } 55 | -------------------------------------------------------------------------------- /lib/master/setup.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | var cluster = require('cluster'), 7 | Client = require('./Client.js'); 8 | 9 | for (var i = 0; i < cluster.workers.length; i++) 10 | new Client(cluster.workers[i]); 11 | 12 | cluster.on('online', function(worker) { 13 | new Client(worker); 14 | }); 15 | -------------------------------------------------------------------------------- /lib/worker/Collection.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | module.exports = Collection; 7 | 8 | 9 | var inherits = require('util').inherits, 10 | EventEmitter = require('events').EventEmitter, 11 | KeyLock = require('./KeyLock.js'), 12 | request = require('./request.js'); 13 | 14 | 15 | function Collection(name) { 16 | this._name = name; 17 | } 18 | 19 | inherits(Collection, EventEmitter); 20 | 21 | Collection.prototype.get = function(key, cb) { 22 | this._request('get', key, null, function(err, msg) { 23 | if (err) 24 | return cb(err); 25 | else if (!msg.json) 26 | return cb(null, undefined); 27 | else 28 | return cb(null, JSON.parse(msg.json)); 29 | }); 30 | }; 31 | 32 | Collection.prototype.set = function(key, value, cb) { 33 | var data = { json: JSON.stringify(value) }; 34 | this._request('set', key, data, cb && function(err, msg) { 35 | return cb(err); 36 | }); 37 | }; 38 | 39 | Collection.prototype.del = function(key, cb) { 40 | return this._request('set', key, null, cb && function(err, msg) { 41 | return cb(err); 42 | }); 43 | }; 44 | 45 | Collection.prototype.acquire = function(key, cb) { 46 | var self = this; 47 | 48 | this._request('acquire', key, null, function(err, msg) { 49 | if (err) 50 | return cb(err); 51 | 52 | var json = msg.json; 53 | var lock = new KeyLock(self, key, json); 54 | cb(null, lock, lock.get()); 55 | }); 56 | }; 57 | 58 | Collection.prototype.configure = function(config) { 59 | var self = this; 60 | 61 | this._request('configure', null, { config: config }, function(err, msg) { 62 | if (err) 63 | self.emit('error', err); 64 | }); 65 | 66 | return this; 67 | }; 68 | 69 | // This function clobbers `data` if specified 70 | Collection.prototype._request = function(method, key, data, cb) { 71 | request(method, this._name, key, data, cb); 72 | }; 73 | -------------------------------------------------------------------------------- /lib/worker/KeyLock.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | module.exports = KeyLock; 7 | 8 | 9 | var request = require('./request.js'); 10 | 11 | 12 | function KeyLock(collection, key, json) { 13 | this._collection = collection; 14 | this._key = key; 15 | this._json = json; 16 | this._updated = false; 17 | this._released = false; 18 | } 19 | 20 | KeyLock.prototype.get = function() { 21 | if (!this._json) 22 | return undefined; 23 | else 24 | return JSON.parse(this._json); 25 | }; 26 | 27 | KeyLock.prototype.set = function(newValue) { 28 | if (this._released) 29 | throw new Error("Can't set after releasing a lock."); 30 | 31 | this._json = JSON.stringify(newValue); 32 | this._updated = true; 33 | }; 34 | 35 | KeyLock.prototype.del = function() { 36 | if (this._released) 37 | throw new Error("Can't delete after releasing a lock."); 38 | 39 | this._json = undefined; 40 | this._updated = true; 41 | }; 42 | 43 | KeyLock.prototype.release = function(cb) { 44 | if (this._released) 45 | throw new Error('KeyLock has already been released.'); 46 | 47 | this._released = true; 48 | 49 | if (!this._updated) 50 | this._collection._request('release', this._key, null, cb && afterRelease); 51 | else 52 | this._collection._request('setRelease', 53 | this._key, 54 | { json: this._json }, 55 | cb && afterRelease); 56 | 57 | function afterRelease(err, msg) { 58 | return cb(err); 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /lib/worker/request.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | module.exports = request; 7 | process.on('message', onMessage); 8 | 9 | 10 | var requestIdCounter = 0; 11 | var requestCallbacks = {}; 12 | 13 | 14 | // This function clobbers `data` if specified 15 | function request(method, collection, key, data, cb) { 16 | data = data || {}; 17 | 18 | data.type = 'DSM_REQUEST'; 19 | data.method = method; 20 | data.collection = collection; 21 | data.key = key; 22 | 23 | if (cb) { 24 | var requestId = getRequestId(); 25 | requestCallbacks[requestId] = cb; 26 | data.requestId = requestId; 27 | } 28 | 29 | process.send(data); 30 | } 31 | 32 | 33 | function onMessage(msg) { 34 | if (msg.type !== 'DSM_REPLY') 35 | return; 36 | 37 | var requestId = msg.requestId; 38 | var cb = requestCallbacks[requestId]; 39 | delete requestCallbacks[requestId]; 40 | 41 | if (msg.err) { 42 | var err = new Error('Master error: ' + msg.err); 43 | return cb(err); 44 | } 45 | 46 | cb(null, msg); 47 | } 48 | 49 | 50 | function getRequestId() { 51 | return ++requestIdCounter; 52 | } 53 | 54 | 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "strong-store-cluster", 3 | "version": "1.0.1", 4 | "description": "In-memory key/value store for the node's native cluster.", 5 | "readmeFilename": "README.md", 6 | "license": "Artistic-2.0", 7 | "keywords": [ 8 | "cluster", 9 | "store" 10 | ], 11 | "author": { 12 | "name": "Bert Belder", 13 | "email": "bert@strongloop.com" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/strongloop/strong-store-cluster.git" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/strongloop/strong-store-cluster/issues" 21 | }, 22 | "main": "index.js", 23 | "scripts": { 24 | "test": "mocha --reporter spec --timeout 5000 --slow 1000 --ui tdd" 25 | }, 26 | "devDependencies": { 27 | "mocha": "~1.9.0" 28 | }, 29 | "engines": { 30 | "node": ">=0.10" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/concurrent-inc.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | var helper = require('./helper/cluster-helper'); 7 | 8 | suite('concurrent increment', function() { 9 | test('master only', function(cb) { 10 | helper.run('do-concurrent-inc', true, 0, 4, cb); 11 | }); 12 | 13 | test('four workers', function(cb) { 14 | helper.run('do-concurrent-inc', false, 4, 4, cb); 15 | }); 16 | 17 | test('master and four workers', function(cb) { 18 | helper.run('do-concurrent-inc', true, 4, 4, cb); 19 | }); 20 | }); 21 | 22 | -------------------------------------------------------------------------------- /test/get-set-del.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | var helper = require('./helper/cluster-helper'); 7 | 8 | suite('get-set-del', function() { 9 | test('master', function(cb) { 10 | helper.run('do-get-set-del', true, 0, 1, cb); 11 | }); 12 | 13 | test('worker', function(cb) { 14 | helper.run('do-get-set-del', false, 1, 1, cb); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/helper/cluster-helper.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | var assert = require('assert'), 7 | cluster = require('cluster'); 8 | 9 | 10 | if (!process.env.CLUSTER_TEST) { 11 | // We're require()'d by the test harness. Export a function that start 12 | // the test. 13 | 14 | exports.run = function(filename, inMaster, workers, concurrency, cb) { 15 | assert(filename); 16 | assert(inMaster || workers); 17 | assert(concurrency > 0); 18 | 19 | var env = {}; 20 | for (var key in process.env) 21 | env[key] = process.env[key]; 22 | 23 | env.CLUSTER_TEST = require.resolve('./' + filename); 24 | env.CLUSTER_TEST_IN_MASTER = inMaster; 25 | env.CLUSTER_TEST_WORKERS = workers; 26 | env.CLUSTER_TEST_CONCURRENCY = concurrency; 27 | 28 | var cp = require('child_process').fork(module.filename, 29 | {env: env, stdio: 'inherit'}); 30 | 31 | cp.on('exit', function(exitCode, termSig) { 32 | assert(exitCode === 0); 33 | assert(!termSig); 34 | 35 | cb(); 36 | }); 37 | }; 38 | 39 | } else { 40 | // We're being spawned as a standalone process. Execute the test and/or spawn 41 | // cluster workers that do. 42 | 43 | var filename = process.env.CLUSTER_TEST, 44 | inMaster = !!+process.env.CLUSTER_TEST_IN_MASTER, 45 | workers = ~~ + process.env.CLUSTER_TEST_WORKERS, 46 | concurrency = ~~ + process.env.CLUSTER_TEST_CONCURRENCY; 47 | 48 | var test = require(filename); 49 | 50 | // Both the master and the worker have .setup() always called once. 51 | test.setup && test.setup(); 52 | 53 | var waiting = 0; 54 | 55 | // If we're the master process, spawn a number of workers. 56 | if (cluster.isMaster && workers) { 57 | for (var i = 0; i < workers; i++) 58 | var worker = cluster.fork(); 59 | 60 | waiting += workers; 61 | 62 | cluster.on('exit', function(worker, exitCode, termSig) { 63 | assert(exitCode === 0); 64 | assert(!termSig); 65 | 66 | done(); 67 | }); 68 | } 69 | 70 | // If we're either a worker, or the master is supposed to run the tests, 71 | // run the test cases. 72 | if (cluster.isWorker || inMaster) { 73 | waiting += concurrency; 74 | 75 | for (var i = 0; i < concurrency; i++) 76 | test.run(done); 77 | } 78 | } 79 | 80 | 81 | function done() { 82 | assert(--waiting >= 0); 83 | if (waiting === 0) 84 | return test.teardown && test.teardown(); 85 | } 86 | 87 | -------------------------------------------------------------------------------- /test/helper/do-concurrent-inc.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | exports.run = run; 7 | exports.teardown = teardown; 8 | 9 | 10 | var ROUNDS = 100; 11 | assert = require('assert'), 12 | cluster = require('cluster'), 13 | store = require('../..'); 14 | 15 | 16 | function run(cb) { 17 | var left = ROUNDS, 18 | coll = store.collection('counter'); 19 | 20 | increment(); 21 | 22 | function increment() { 23 | coll.acquire('counter', function(err, lock, val) { 24 | assert(!err); 25 | 26 | if (!val) 27 | val = 1; 28 | else 29 | val++; 30 | 31 | lock.set(val); 32 | lock.release(); 33 | 34 | if (--left > 0) 35 | increment(); 36 | else 37 | cb(); 38 | }); 39 | } 40 | } 41 | 42 | 43 | function teardown() { 44 | if (cluster.isWorker) 45 | process._channel.unref(); 46 | 47 | if (cluster.isMaster) { 48 | store.collection('counter').get('counter', function(err, value) { 49 | assert(value % ROUNDS === 0); 50 | assert(value >= ROUNDS); 51 | }); 52 | } 53 | } 54 | 55 | 56 | -------------------------------------------------------------------------------- /test/helper/do-get-set-del.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | exports.run = run; 7 | exports.teardown = teardown; 8 | 9 | 10 | var assert = require('assert'), 11 | cluster = require('cluster'), 12 | store = require('../..'); 13 | 14 | 15 | function run(cb) { 16 | var testsRun = 0; 17 | 18 | testWith('test1', 'key1', 'zulis', onDone); 19 | testWith('test2', 'quux', 'stoll', onDone); 20 | testWith('test2', 'key1', 'urals', onDone); 21 | testWith('test2', 'key2', 'quipp', onDone); 22 | 23 | function onDone() { 24 | if (++testsRun === 4) 25 | cb(); 26 | } 27 | } 28 | 29 | 30 | function testWith(collectionName, key, testValue, cb) { 31 | var coll = store.collection(collectionName); 32 | 33 | coll.get(key, function(err, value) { 34 | assert(!err); 35 | assert(value === undefined); 36 | 37 | coll.set(key, testValue, function(err) { 38 | assert(!err); 39 | 40 | coll.get(key, function(err, value) { 41 | assert(!err); 42 | assert(value === testValue); 43 | 44 | coll.del(key, function(err) { 45 | assert(!err); 46 | 47 | coll.get(key, function(err, value) { 48 | assert(!err); 49 | assert(value === undefined); 50 | 51 | cb(); 52 | }); 53 | }); 54 | }); 55 | }); 56 | }); 57 | } 58 | 59 | 60 | 61 | function teardown() { 62 | if (cluster.isWorker) 63 | process._channel.unref(); 64 | } 65 | -------------------------------------------------------------------------------- /test/helper/do-lock-get-set-del.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | exports.run = run; 7 | exports.teardown = teardown; 8 | 9 | 10 | var assert = require('assert'), 11 | cluster = require('cluster'), 12 | store = require('../..'); 13 | 14 | 15 | function run(cb) { 16 | var testsRun = 0; 17 | 18 | testWith('test1', 'key1', {foo: 'zulis'}, onDone); 19 | testWith('test2', 'quux', ['stoll'], onDone); 20 | testWith('test2', 'key1', 'urals', onDone); 21 | testWith('test2', 'key2', 42, onDone); 22 | 23 | function onDone() { 24 | if (++testsRun === 4) 25 | cb(); 26 | } 27 | } 28 | 29 | 30 | function testWith(collectionName, key, testValue, cb) { 31 | var coll = store.collection(collectionName); 32 | 33 | coll.set(key, testValue, function(err, value) { 34 | assert(!err); 35 | 36 | coll.acquire(key, function(err, lock, value) { 37 | assert(!err); 38 | 39 | assert.deepEqual(testValue, value); 40 | assert.deepEqual(testValue, lock.get()); 41 | 42 | // Non-primitive values should be deep-cloned. 43 | if (typeof testValue === 'object') { 44 | assert(testValue !== value); 45 | assert(testValue !== lock.get()); 46 | } 47 | 48 | lock.set('other'); 49 | assert('other' === lock.get()); 50 | 51 | lock.release(function(err) { 52 | assert(!err); 53 | }); 54 | 55 | coll.acquire(key, function(err, lock, value) { 56 | assert(!err); 57 | 58 | assert('other' === value); 59 | assert('other' === lock.get()); 60 | 61 | lock.del(); 62 | assert(undefined === lock.get()); 63 | 64 | lock.release(function(err) { 65 | assert(!err); 66 | }); 67 | 68 | coll.get(key, function(err, value) { 69 | assert(!err); 70 | assert(undefined === value); 71 | 72 | // That was it! 73 | cb(); 74 | }); 75 | }); 76 | }); 77 | }); 78 | } 79 | 80 | 81 | function teardown() { 82 | if (cluster.isWorker) 83 | process._channel.unref(); 84 | } 85 | -------------------------------------------------------------------------------- /test/lock-get-set-del.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-store-cluster 3 | // This file is licensed under the Artistic License 2.0. 4 | // License text available at https://opensource.org/licenses/Artistic-2.0 5 | 6 | var helper = require('./helper/cluster-helper'); 7 | 8 | suite('lock-get-set-del', function() { 9 | test('master', function(cb) { 10 | helper.run('do-lock-get-set-del', true, 0, 1, cb); 11 | }); 12 | 13 | test('worker', function(cb) { 14 | helper.run('do-lock-get-set-del', false, 1, 1, cb); 15 | }); 16 | }); 17 | --------------------------------------------------------------------------------