├── .gitignore ├── .jshintrc ├── .travis.yml ├── CHANGES.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── docs.json ├── index.js ├── lib └── cluster-store.js ├── package.json └── test └── cluster-store.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | 16 | strong-cluster-store-*.tgz 17 | coverage.html 18 | node_modules 19 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "camelcase": true 3 | , "quotmark": "single" 4 | , "eqnull": true 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.11" 5 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | 2016-05-03, Version 0.1.8 2 | ========================= 3 | 4 | * update/insert copyright notices (Ryan Graham) 5 | 6 | * re-license as Artistic-2.0 only (Ryan Graham) 7 | 8 | 9 | 2016-04-11, Version 0.1.7 10 | ========================= 11 | 12 | * Refer to licenses with a link (Sam Roberts) 13 | 14 | 15 | 2015-10-01, Version 0.1.6 16 | ========================= 17 | 18 | * Use strongloop conventions for licensing (Sam Roberts) 19 | 20 | 21 | 2015-06-03, Version 0.1.5 22 | ========================= 23 | 24 | * fixup debug pattern (Sam Roberts) 25 | 26 | * package: update dependencies to latest (Sam Roberts) 27 | 28 | * test: don't bother disconnecting workers (Sam Roberts) 29 | 30 | * Fix bad CLA URL in CONTRIBUTING.md (Ryan Graham) 31 | 32 | * Update contribution guidelines (Ryan Graham) 33 | 34 | * Update package license to match LICENSE.md (Sam Roberts) 35 | 36 | * Update link to doc (Rand McKinney) 37 | 38 | * doc: add CONTRIBUTING.md and LICENSE.md (Ben Noordhuis) 39 | 40 | * Apply Dual MIT/StrongLoop license (Sam Roberts) 41 | 42 | * Replace old README with link to docs. (Rand McKinney) 43 | 44 | * Update docs.json (Rand McKinney) 45 | 46 | * remove unused blanket dependencies (Sam Roberts) 47 | 48 | 49 | 2013-10-29, Version 0.1.4 50 | ========================= 51 | 52 | * test: disabled test failing due to socket.io bug (Miroslav Bajtos) 53 | 54 | * Fix syntax highlighting, language is sh not Shell (Rand McKinney) 55 | 56 | * README: warning about race condition in socket.io (Miroslav Bajtos) 57 | 58 | * lib,test: added debug logs (Miroslav Bajtos) 59 | 60 | * ClusterStore: fixed incorrect usage of strong-mq (Miroslav Bajtos) 61 | 62 | * lib/cluster-store: improved API doc (Miroslav Bajtos) 63 | 64 | * package.json: bumped up version number (Miroslav Bajtos) 65 | 66 | * Making json valid. (Edmond Meinfelder) 67 | 68 | * Adding docs.json for documentation. (Edmond Meinfelder) 69 | 70 | * update coverage filename (slnode) 71 | 72 | 73 | 2013-07-15, Version 0.1.2 74 | ========================= 75 | 76 | * started work on v0.1.2 (Miroslav Bajtos) 77 | 78 | 79 | 2013-07-15, Version 0.1.1 80 | ========================= 81 | 82 | * package.json: use peerDependencies (Miroslav Bajtos) 83 | 84 | * started work on v0.1.1 (Miroslav Bajtos) 85 | 86 | 87 | 2013-07-12, Version 0.1.0 88 | ========================= 89 | 90 | * First release! 91 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Contributing ### 2 | 3 | Thank you for your interest in `strong-cluster-socket.io-store`, an open source project 4 | administered by StrongLoop. 5 | 6 | Contributing to `strong-cluster-socket.io-store` 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-cluster-socket.io-store) 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,2016. All Rights Reserved. 2 | Node module: strong-cluster-socket.io-store 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Socket.io Store for Cluster 2 | 3 | [![Build Status](https://travis-ci.org/strongloop/strong-cluster-socket.io-store.png?branch=master)](https://travis-ci.org/strongloop/strong-cluster-socket.io-store) 4 | [![NPM version](https://badge.fury.io/js/strong-cluster-socket.io-store.png)](http://badge.fury.io/js/strong-cluster-socket.io-store) 5 | 6 | ## Overview 7 | 8 | Strong-cluster-socket.io-store is an implementation of socket.io store 9 | using node's native cluster messaging. It provides an easy solution 10 | for running socket.io server in a node cluster. 11 | 12 | ## Installation 13 | 14 | $ npm install strong-cluster-socket.io-store 15 | 16 | ### Documentation 17 | 18 | Please see the [official StrongLoop documentation](http://docs.strongloop.com/display/SOPS/Socket+IO+store+for+clusters). 19 | 20 | -------------------------------------------------------------------------------- /docs.json: -------------------------------------------------------------------------------- 1 | { 2 | "content": [ 3 | { "title": "API", "depth": 2 }, 4 | "lib/cluster-store.js" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-cluster-socket.io-store 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 = require('./lib/cluster-store.js'); 7 | -------------------------------------------------------------------------------- /lib/cluster-store.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013. All Rights Reserved. 2 | // Node module: strong-cluster-socket.io-store 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 inherits = require('util').inherits; 7 | var cluster = require('cluster'); 8 | var mq = require('strong-mq'); 9 | var NativeStore = require('strong-store-cluster'); 10 | var debug = require('debug')('strong-cluster-socket.io-store'); 11 | 12 | /** 13 | * Documentation marker for explicit setup of the shared-state server 14 | * in the master process. The initialization happens when this module 15 | * is required, thus calling this function is entirely optional. 16 | */ 17 | function setup() { 18 | // no-op 19 | } 20 | 21 | /** 22 | * Return the `ClusterStore` constructor. 23 | * 24 | * @param {Object} io socket.io module as returned by `require('socket.io')` 25 | * @return {function} 26 | */ 27 | module.exports = function(io) { 28 | 29 | /** 30 | * Initialize ClusterStore with the given `options`. 31 | * 32 | * ClusterStore is an implementation of socket.io store using node's native 33 | * cluster messaging. 34 | * 35 | * @param {Object} options 36 | * @constructor 37 | * @extends {io.Store} 38 | */ 39 | function ClusterStore(opts) { 40 | io.Store.call(this, opts); 41 | this._connection = mq.create({ provider: 'native' }); 42 | this._connection.open(); 43 | this._pub = this._connection.createPubQueue('socket.io'); 44 | this._sub = this._connection.createSubQueue('socket.io'); 45 | this._sub.subscribe('', this._onMessage.bind(this)); 46 | this._clientId = cluster.worker.id; 47 | this._subscribers = {}; 48 | this._collection = NativeStore.collection('strong-cluster-socket.io-session-store'); 49 | 50 | // Strong-store-cluster does not support per-key expiration value. 51 | // We can't access io configuration at this moment to fetch 52 | // the configured expiration time. 53 | // We use the default value of 15 seconds for now. 54 | this._collection.configure({ expireKeys: 15 }); 55 | } 56 | 57 | inherits(ClusterStore, io.Store); 58 | 59 | /** 60 | * Publish a message to a channel. 61 | * @param {string} name Channel name. 62 | * @param {...Object} var_args Message arguments. 63 | * @private 64 | */ 65 | ClusterStore.prototype.publish = function(name) { 66 | var args = Array.prototype.slice.call(arguments, 1); 67 | var data = { 68 | clientId: this._clientId, 69 | name: name, 70 | args: args 71 | }; 72 | debug('publish %j', data); 73 | this._pub.publish(data); 74 | }; 75 | 76 | /** 77 | * Subscribe to messages in a channel. 78 | * @param {string} name Channel name. 79 | * @param {function(...[Object])} consumer Message consumer. 80 | * @param {function(Error)} fn Callback on done. 81 | * @private 82 | */ 83 | ClusterStore.prototype.subscribe = function(name, consumer, fn) { 84 | this._subscribers[name] = consumer; 85 | if (fn) process.nextTick(fn); 86 | }; 87 | 88 | /** 89 | * Unsubscribe from messages in a channel. 90 | * @param {string} name Channel name. 91 | * @param {function(Error|string)} fn Callback on done. 92 | * @private 93 | */ 94 | ClusterStore.prototype.unsubscribe = function(name, fn) { 95 | delete this._subscribers[name]; 96 | if (fn) process.nextTick(fn); 97 | }; 98 | 99 | /** 100 | * Message handler for strong-mq subscribe channel. 101 | * The handler dispatches the message to the consumer registered 102 | * for message's channel. 103 | * @param {{clientId:string, name:string, args:Array}} data 104 | * @private 105 | */ 106 | ClusterStore.prototype._onMessage = function(data) { 107 | if (data.clientId === this._clientId) return; 108 | var consumer = this._subscribers[data.name]; 109 | if (consumer !== undefined) { 110 | debug('consume %j', data); 111 | consumer.apply(null, data.args); 112 | } 113 | }; 114 | 115 | /** 116 | * Initialize Client for given `store` and client `id`. 117 | * 118 | * @param {ClusterStore} store 119 | * @param {string} id 120 | * @constructor 121 | * @extends {io.Store.Client} 122 | * @private 123 | */ 124 | function ClusterClient(store, id) { 125 | io.Store.Client.apply(this, arguments); 126 | } 127 | 128 | inherits(ClusterClient, io.Store.Client); 129 | 130 | /** 131 | * Acquire a keylock or get an already acquired one. 132 | * Calls `fn` with keylock and hashmap value (client data). 133 | * @param {function(Error, KeyLock, Object)} fn 134 | * @private 135 | */ 136 | ClusterClient.prototype._getKeylock = function(fn) { 137 | var self = this; 138 | if (self._keylock) { 139 | return process.nextTick(function() { 140 | fn(null, self._keylock, self._hashmap); 141 | }); 142 | } 143 | 144 | self.store._collection.acquire( 145 | self.id, 146 | function(err, keylock, value) { 147 | if (err) return fn(err); 148 | self._keylock = keylock; 149 | self._hashmap = value || {}; 150 | fn(null, self._keylock, self._hashmap); 151 | } 152 | ); 153 | }; 154 | 155 | /** 156 | * Get the value of a key. 157 | * @param {string} key 158 | * @param {function(Error, *)} fn 159 | * @private 160 | */ 161 | ClusterClient.prototype.get = function(key, fn) { 162 | this._getKeylock(function(err, keylock, hashmap) { 163 | if (err) return fn(err); 164 | fn(null, hashmap[key]); 165 | }); 166 | }; 167 | 168 | 169 | /** 170 | * Set a value for a key. 171 | * @param {string} key 172 | * @param {*} value 173 | * @param {function(Error)} fn 174 | * @private 175 | */ 176 | ClusterClient.prototype.set = function(key, value, fn) { 177 | this._getKeylock(function(err, keylock, hashmap) { 178 | if (err) return fn(err); 179 | hashmap[key] = value; 180 | fn(); 181 | }); 182 | }; 183 | 184 | 185 | /** 186 | * Check if there is a value associated with a key. 187 | * @param {string} key 188 | * @param {function(Error,boolean)} fn 189 | * @private 190 | */ 191 | ClusterClient.prototype.has = function(key, fn) { 192 | this._getKeylock(function(err, keylock, hashmap) { 193 | if (err) return fn(err); 194 | fn(null, hashmap.hasOwnProperty(key)); 195 | }); 196 | }; 197 | 198 | /** 199 | * Delete a key and it's value. 200 | * @param {string} key 201 | * @param {function(Error)} fn 202 | * @private 203 | */ 204 | ClusterClient.prototype.del = function(key, fn) { 205 | this._getKeylock(function(err, keylock, hashmap) { 206 | if (err) return fn(err); 207 | delete hashmap[key]; 208 | fn(); 209 | }); 210 | }; 211 | 212 | /** 213 | * Destroy the client. 214 | * @param {?number} expiration number of seconds to expire client data 215 | * @private 216 | */ 217 | ClusterClient.prototype.destroy = function(expiration) { 218 | var self = this; 219 | this._getKeylock(function(err, keylock, hashmap) { 220 | if (err) { 221 | console.error('Cannot destroy socket.io client session.', err); 222 | return; 223 | } 224 | if (expiration) { 225 | keylock.set(hashmap); 226 | // Calling release() starts an expiry timer in master 227 | // which will delete the record if it is not accessed 228 | // within the timeout. Unfortunately it's not possible 229 | // to configure the timeout length at the moment. 230 | keylock.release(); 231 | } else { 232 | keylock.del(self.id); 233 | keylock.release(); 234 | } 235 | }); 236 | }; 237 | 238 | ClusterStore.Client = ClusterClient; 239 | 240 | /** 241 | * Same as `setup()` (see above). 242 | */ 243 | ClusterStore.setup = setup; 244 | 245 | if (cluster.isMaster) { 246 | // See https://github.com/strongloop/sl-mq/issues/8 247 | mq.create({provider: 'native'}).close(); 248 | } 249 | 250 | return ClusterStore; 251 | }; 252 | 253 | module.exports.setup = setup; 254 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "strong-cluster-socket.io-store", 3 | "version": "0.1.8", 4 | "description": "Implementation of socket.io store using node's native cluster messaging", 5 | "license": "Artistic-2.0", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "mocha --reporter spec", 9 | "lint": "jshint *.js test lib" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/strongloop/strong-cluster-socket.io-store.git" 14 | }, 15 | "keywords": [ 16 | "socket.io", 17 | "cluster", 18 | "store" 19 | ], 20 | "author": { 21 | "name": "Miroslav Bajtos", 22 | "email": "miroslav@strongloop.com" 23 | }, 24 | "dependencies": { 25 | "debug": "^2.x" 26 | }, 27 | "peerDependencies": { 28 | "strong-mq": "^1.x", 29 | "strong-store-cluster": "^1.x" 30 | }, 31 | "devDependencies": { 32 | "mocha": "~1.9.0", 33 | "jshint": "~2.0.1", 34 | "chai": "~1.7.2", 35 | "socket.io": "~0.9.16", 36 | "socket.io-client": "~0.9.16", 37 | "strong-mq": "^1.x", 38 | "strong-store-cluster": "^1.x" 39 | }, 40 | "engines": { 41 | "node": ">=0.10.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/cluster-store.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2013,2015. All Rights Reserved. 2 | // Node module: strong-cluster-socket.io-store 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 | var expect = require('chai').expect; 8 | var ioServer = require('socket.io'); 9 | var ioClient = require('socket.io-client'); 10 | var debug = require('debug')('test'); 11 | 12 | var ClusterStore = require('..')(ioServer); 13 | 14 | var serverUrl; 15 | 16 | // verify we can call setup without ioServer in master and workers 17 | require('..').setup(); 18 | 19 | if (cluster.isWorker) { 20 | startSocketIoServer(); 21 | return; 22 | } 23 | 24 | 25 | describe('clustered socket.io', function() { 26 | before(setupWorkers); 27 | 28 | describe('server', function() { 29 | // NOTE(bajtos): There is a bug in socket.io implementation (#952) 30 | // that makes it impossible to get socket.io working in cluster 31 | // without session affinity. 32 | // The following test is exposing this problem and is failing on slower 33 | // machines. 34 | it.skip('shares hand-shaken connections', function(done) { 35 | var client = ioClient.connect(serverUrl, { reconnect: false }); 36 | client.on('error', function(err) { done(err); }); 37 | client.on('connect', function() { client.disconnect(); done(); }); 38 | }); 39 | }); 40 | 41 | // NOTE(bajtos): following tests do not verify that data is actually shared 42 | // between the workers, because I was not able to force socket.io client 43 | // to reconnect using the same client id. 44 | describe('per-client store', function() { 45 | var client; 46 | beforeEach(createClient); 47 | afterEach(closeClient); 48 | 49 | it('shares user data', function(done) { 50 | var PAYLOAD = 'a-string-data'; 51 | client.emit('save', PAYLOAD); 52 | client.on('saved', function() { 53 | client.emit('load'); 54 | }); 55 | 56 | client.on('loaded', function(data) { 57 | expect(data).to.equal(PAYLOAD); 58 | done(); 59 | }); 60 | }); 61 | 62 | it('checks for existence of a shared entry', function(done) { 63 | client.emit('save', 'a-value'); 64 | client.on('saved', function() { 65 | client.emit('check'); 66 | }); 67 | 68 | client.on('checked', function(data) { 69 | expect(data).to.equal(true); 70 | done(); 71 | }); 72 | }); 73 | 74 | it('deletes a shared entry', function(done) { 75 | client.emit('save', 'a-value'); 76 | client.on('saved', function() { 77 | client.emit('delete'); 78 | }); 79 | client.on('deleted', function(data) { 80 | client.emit('check'); 81 | }); 82 | 83 | client.on('checked', function(data) { 84 | expect(data).to.equal(false); 85 | done(); 86 | }); 87 | }); 88 | 89 | function createClient(done) { 90 | debug('creating a new socket.io client'); 91 | client = ioClient.connect( 92 | serverUrl, 93 | { 94 | reconnect: false, 95 | 'force new connection': true 96 | } 97 | ); 98 | 99 | client.on('error', function(err) { 100 | debug('client error', err); 101 | done(err); 102 | }); 103 | 104 | client.on('connect', function() { 105 | debug('client connected'); 106 | done(); 107 | }); 108 | } 109 | 110 | function closeClient(done) { 111 | client.once('disconnect', function() { 112 | debug('client disconnected'); 113 | done(); 114 | }); 115 | client.disconnect(); 116 | } 117 | }); 118 | }); 119 | 120 | var WORKER_COUNT = 2; 121 | 122 | function getNumberOfWorkers() { 123 | return Object.keys(cluster.workers).length; 124 | } 125 | 126 | function setupWorkers(done) { 127 | if (getNumberOfWorkers() > 0) { 128 | var msg = 'Cannot setup workers: there are already other workers running.'; 129 | return done(new Error(msg)); 130 | } 131 | 132 | cluster.setupMaster({ exec: __filename }); 133 | ClusterStore.setup(); 134 | 135 | var workersListening = 0; 136 | cluster.on('listening', function(w, addr) { 137 | if (!serverUrl) serverUrl = 'http://localhost:' + addr.port; 138 | 139 | workersListening++; 140 | if (workersListening == WORKER_COUNT) { 141 | done(); 142 | } 143 | }); 144 | 145 | for (var i = 0; i < WORKER_COUNT; i++) { 146 | cluster.fork(); 147 | } 148 | } 149 | 150 | function startSocketIoServer() { 151 | var PORT = 0; // Let the OS pick any available port 152 | var options = { store: new ClusterStore() }; 153 | var server = ioServer.listen(PORT, options); 154 | 155 | server.on('connection', function(socket) { 156 | socket.on('save', function(data) { 157 | socket.set('test-data', data, function() { socket.emit('saved'); }); 158 | }); 159 | 160 | socket.on('load', function() { 161 | socket.get('test-data', function(err, result) { 162 | socket.emit('loaded', result); 163 | }); 164 | }); 165 | 166 | socket.on('check', function() { 167 | socket.has('test-data', function(err, result) { 168 | socket.emit('checked', result); 169 | }); 170 | }); 171 | 172 | socket.on('delete', function() { 173 | socket.del('test-data', function() { 174 | socket.emit('deleted'); 175 | }); 176 | }); 177 | }); 178 | } 179 | --------------------------------------------------------------------------------