Compare commits
No commits in common. "master" and "v1.0.3" have entirely different histories.
14
.gitignore
vendored
14
.gitignore
vendored
@ -1,7 +1,6 @@
|
|||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
pids
|
pids
|
||||||
@ -14,9 +13,6 @@ lib-cov
|
|||||||
# Coverage directory used by tools like istanbul
|
# Coverage directory used by tools like istanbul
|
||||||
coverage
|
coverage
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
.grunt
|
.grunt
|
||||||
|
|
||||||
@ -26,12 +22,6 @@ coverage
|
|||||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
build/Release
|
build/Release
|
||||||
|
|
||||||
# Dependency directories
|
# Dependency directory
|
||||||
|
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||||
node_modules
|
node_modules
|
||||||
jspm_packages
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|||||||
215
LICENSE
215
LICENSE
@ -1,21 +1,202 @@
|
|||||||
MIT License
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
Copyright (c) 2016 AJ ONeal
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
1. Definitions.
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
copies or substantial portions of the Software.
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|||||||
160
README.md
160
README.md
@ -1,104 +1,74 @@
|
|||||||
cluster-store
|
Cluster Store
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Makes any storage strategy similar to `express/session` useful in both `cluster` and non-`cluster` environments
|
A very simple in-memory object store for use with node cluster
|
||||||
by wrapping it with `cluster-rpc`.
|
(or even completely and unrelated node processes).
|
||||||
|
|
||||||
Also works with **level-session-store** (leveldb), **connect-session-knex** (SQLite3), **session-file-store** (fs),
|
Node.js runs on a single core, which isn't very effective.
|
||||||
and any other embedded / in-process store.
|
|
||||||
|
|
||||||
Note: Most people would probably prefer to just use Redis rather than wrap a dumb memstore as a service...
|
You can run multiple Node.js instances to take advantage of multiple cores,
|
||||||
but I am not most people.
|
but if you do that, you can't share memory between processes.
|
||||||
|
|
||||||
Install
|
This module will either run client-server style in environments that benefit from it
|
||||||
=======
|
(such as the Raspberry Pi 2 with 4 cores), or in-process for environments that don't
|
||||||
|
(such as the Raspberry Pi B and B+).
|
||||||
|
|
||||||
```
|
**Note**: Most people would probably prefer to just use Redis rather than
|
||||||
npm install --save cluster-store@2.x
|
wrap a dumb memstore as a service... but I am not most people.
|
||||||
```
|
|
||||||
|
|
||||||
v1.x vs v2.x
|
Also works with **level-session-store** (leveldb), **connect-session-knex** (SQLite3),
|
||||||
------------
|
**session-file-store** (fs), and any other embedded / in-process store.
|
||||||
|
|
||||||
The [old v1](https://github.com/coolaj86/cluster-store/tree/v1.x)
|
|
||||||
used `ws` which makes it usable when clustering node processes without using `cluster`.
|
|
||||||
|
|
||||||
If you need that functionaliy, use v1 instead of v2.
|
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
=====
|
=====
|
||||||
|
|
||||||
In its simplest form, you use this module nearly exactly the way you would
|
The default behavior is to try to connect to a master and, if that fails, to become the master.
|
||||||
the any other storage module, with the exception that you must wait for
|
|
||||||
the inter-process initialization to complete.
|
|
||||||
|
|
||||||
When not using any of the options the usage is the same for the master and the worker:
|
However, if you are in fact using the `cluster` rather than spinning up random instances,
|
||||||
|
you'll probably prefer to use this pattern:
|
||||||
```javascript
|
|
||||||
require('cluster-store').create().then(function (store) {
|
|
||||||
// initialization is now complete
|
|
||||||
store.set('foo', 'bar');
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### standalone (non-cluster)
|
|
||||||
--------------
|
|
||||||
|
|
||||||
There is no disadvantage to using this module standalone.
|
|
||||||
The additional overhead of inter-process communication is only added when
|
|
||||||
a worker is added.
|
|
||||||
|
|
||||||
As such, the standalone usage is identical to usage in master process, as seen below.
|
|
||||||
|
|
||||||
### master
|
|
||||||
|
|
||||||
In the **master** process you will create the real store instance.
|
|
||||||
|
|
||||||
If you need to manually specify which worker will be enabled for this funcitonality
|
|
||||||
you must set `addOnFork` to `false` and call `addWorker()` manually.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
|
```js
|
||||||
var cluster = require('cluster');
|
var cluster = require('cluster');
|
||||||
|
var cstore = require('cluster-store');
|
||||||
|
var numCores = require('os').cpus().length;
|
||||||
|
|
||||||
var cstore = require('cluster-store/master').create({
|
var opts = {
|
||||||
name: 'foo-store' // necessary when using multiple instances
|
sock: '/tmp/memstore.sock'
|
||||||
, store: null // use default in-memory store
|
|
||||||
, addOnFork: true // default
|
|
||||||
});
|
|
||||||
|
|
||||||
// if you addOnFork is set to false you can add specific forks manually
|
// If left 'null' or 'undefined' this defaults to a similar memstore
|
||||||
//cstore.addWorker(cluster.fork());
|
// with no special logic for 'cookie' or 'expires'
|
||||||
|
, store: cluster.isMaster && new require('express-session/session/memory')()
|
||||||
|
|
||||||
cstore.then(function (store) {
|
// a good default to use for instances where you might want
|
||||||
store.set('foo', 'bar');
|
// to cluster or to run standalone, but with the same API
|
||||||
});
|
, serve: cluster.isMaster
|
||||||
```
|
, connect: cluster.isWorker
|
||||||
|
, standalone: (1 === numCores) // overrides serve and connect
|
||||||
|
};
|
||||||
|
|
||||||
Note: `store` can be replaced with any `express/session`-compatible store, such as:
|
cstore.create(opts).then(function (store) {
|
||||||
|
// same api as new new require('express-session/session/memory')(
|
||||||
|
|
||||||
* `new require('express-session/session/memory')()`
|
store.get(id, function (err, data) {
|
||||||
* `require('level-session-store')(session)`
|
console.log(data);
|
||||||
* and others
|
|
||||||
|
|
||||||
### worker
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// retrieve the instance
|
|
||||||
var cstore = require('cluster-store/worker').create({
|
|
||||||
name: 'foo-store'
|
|
||||||
});
|
|
||||||
|
|
||||||
cstore.then(function (store) {
|
|
||||||
store.get('foo', function (err, result) {
|
|
||||||
console.log(result);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// app.use(expressSession({ secret: 'keyboard cat', store: store }));
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('unhandledPromiseRejection', function (err) {
|
||||||
|
console.error('Unhandled Promise Rejection');
|
||||||
|
console.error(err);
|
||||||
|
console.error(err.stack);
|
||||||
|
|
||||||
|
throw err;
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you wish to always use clustering, even on a single core system, see `test-cluster.js`.
|
||||||
|
|
||||||
|
Likewise, if you wish to use standalone mode in a particular worker process see `test-standalone.js`.
|
||||||
|
|
||||||
API
|
API
|
||||||
===
|
===
|
||||||
|
|
||||||
@ -124,28 +94,22 @@ Helpers
|
|||||||
|
|
||||||
See <https://github.com/expressjs/session#session-store-implementation>@4.x for full details
|
See <https://github.com/expressjs/session#session-store-implementation>@4.x for full details
|
||||||
|
|
||||||
Example
|
Standalone / Master Mode is in-process
|
||||||
=======
|
========================
|
||||||
|
|
||||||
```javascript
|
The `master` in the cluster (meaning `opts.serve = true`) will directly hold the specified store
|
||||||
'use strict';
|
(a simple memory store by default, or `express-session/session/memory` in the example above)
|
||||||
|
|
||||||
var cluster = require('cluster');
|
Likewise, when only one process is being used (`opts.standalone = true`) the listener is
|
||||||
|
not started and API is completely in-process.
|
||||||
|
|
||||||
require('cluster-store').create({
|
If you take a look at `memstore.js` you'll see that it's a rather simple memory store instance.
|
||||||
name: 'foo-store'
|
|
||||||
}).then(function (store) {
|
|
||||||
if (cluster.isMaster) {
|
|
||||||
store.set('foo', 'bar');
|
|
||||||
}
|
|
||||||
|
|
||||||
store.get('foo', function (err, result) {
|
Security Warning
|
||||||
console.log(result);
|
================
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (cluster.isMaster) {
|
Note that any application on the system could connect to the socket.
|
||||||
cluster.fork();
|
|
||||||
cluster.fork();
|
In the future I may add a `secret` field in the options object to be
|
||||||
}
|
used for authentication across processes. This would not be difficult,
|
||||||
```
|
it's just not necessary for my use case at the moment.
|
||||||
|
|||||||
199
client.js
Normal file
199
client.js
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/*global Promise*/
|
||||||
|
|
||||||
|
function startServer(opts) {
|
||||||
|
return require('./server').create(opts).then(function (server) {
|
||||||
|
// this process doesn't need to connect to itself
|
||||||
|
// through a socket
|
||||||
|
return server.masterClient;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConnection(opts) {
|
||||||
|
return new Promise(function (resolve) {
|
||||||
|
//setTimeout(function () {
|
||||||
|
var WebSocket = require('ws');
|
||||||
|
var ws = new WebSocket('ws+unix:' + opts.sock);
|
||||||
|
|
||||||
|
ws.on('error', function (err) {
|
||||||
|
console.error('[ERROR] ws connection failed, retrying');
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
|
function retry() {
|
||||||
|
setTimeout(function () {
|
||||||
|
getConnection(opts).then(resolve, retry);
|
||||||
|
}, 100 + (parseInt(require('crypto').randomBytes(2).toString('hex'), 16) % 250));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opts.connect && ('ENOENT' === err.code || 'ECONNREFUSED' === err.code)) {
|
||||||
|
console.log('[NO SERVER] attempting to create a server #######################');
|
||||||
|
return startServer(opts).then(function (client) {
|
||||||
|
// ws.masterClient = client;
|
||||||
|
resolve({ masterClient: client });
|
||||||
|
}, function () {
|
||||||
|
retry();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
retry();
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
ws.on('open', function () {
|
||||||
|
resolve(ws);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
ws.___listeners = [];
|
||||||
|
ws.on('message', function (data) {
|
||||||
|
ws.___listeners.forEach(function (fn) {
|
||||||
|
try {
|
||||||
|
fn(data);
|
||||||
|
} catch(e) {
|
||||||
|
console.error("[ERROR] ws.on('message', fn) (multi-callback)");
|
||||||
|
console.error(e);
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function onInitMessage(str) {
|
||||||
|
// TODO there's no way to remove a listener... what to do?
|
||||||
|
var data;
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = JSON.parse(str);
|
||||||
|
} catch(e) {
|
||||||
|
console.error('[ERROR]');
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('methods' !== data.type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = ws.___listeners.indexOf(onInitMessage);
|
||||||
|
ws.___listeners.splice(index, 1);
|
||||||
|
ws._methods = data.methods;
|
||||||
|
|
||||||
|
resolve(ws);
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.___listeners.push(onInitMessage);
|
||||||
|
//}, 100 + (Math.random() * 250));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function create(opts) {
|
||||||
|
if (!opts.sock) {
|
||||||
|
opts.sock = '/tmp/memstore' + '.sock';
|
||||||
|
}
|
||||||
|
|
||||||
|
var promise;
|
||||||
|
var numcpus = require('os').cpus().length;
|
||||||
|
if (opts.standalone || (1 === numcpus && !opts.serve && !opts.connect)) {
|
||||||
|
return require('./memstore').create(opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function retryServe() {
|
||||||
|
return startServer(opts).then(function (client) {
|
||||||
|
// ws.masterClient = client;
|
||||||
|
return { masterClient: client };
|
||||||
|
}, function (err) {
|
||||||
|
console.error('[ERROR] retryServe()');
|
||||||
|
console.error(err);
|
||||||
|
retryServe();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.serve) {
|
||||||
|
promise = retryServe();
|
||||||
|
} else {
|
||||||
|
promise = getConnection(opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO maybe use HTTP POST instead?
|
||||||
|
return promise.then(function (ws) {
|
||||||
|
if (ws.masterClient) {
|
||||||
|
return ws.masterClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
var db = {};
|
||||||
|
|
||||||
|
function rpc(fname, args) {
|
||||||
|
var id;
|
||||||
|
var cb;
|
||||||
|
|
||||||
|
if ('function' === typeof args[args.length - 1]) {
|
||||||
|
// TODO if off, search for cb and derive id from previous onMessage
|
||||||
|
id = require('crypto').randomBytes(16).toString('hex');
|
||||||
|
cb = args.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: 'rpc'
|
||||||
|
, func: fname
|
||||||
|
, args: args
|
||||||
|
, hasCallback: !!cb
|
||||||
|
, filename: opts.filename
|
||||||
|
, id: id
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!cb) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMessage(data) {
|
||||||
|
var cmd;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cmd = JSON.parse(data.toString('utf8'));
|
||||||
|
} catch(e) {
|
||||||
|
console.error('[ERROR] in client, from sql server parse json');
|
||||||
|
console.error(e);
|
||||||
|
console.error(data);
|
||||||
|
console.error();
|
||||||
|
|
||||||
|
//ws.send(JSON.stringify({ type: 'error', value: { message: e.message, code: "E_PARSE_JSON" } }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.id !== id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// TODO not sure how to handle 'emit' or 'off'...
|
||||||
|
// it'll just be broken for now
|
||||||
|
if ('off' === fname || 'remove.*Listener'.test(fname)) {
|
||||||
|
var index = ws.___listeners.indexOf(onMessage);
|
||||||
|
ws.___listeners.splice(index, 1);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ('on' !== fname && ! /add.*Listener/.test(fname)) {
|
||||||
|
var index = ws.___listeners.indexOf(onMessage);
|
||||||
|
ws.___listeners.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
cb.apply(cmd.this, cmd.args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO search index by cb for 'off'
|
||||||
|
// and pass it along to the rpc with the original id
|
||||||
|
onMessage.__cb = cb;
|
||||||
|
onMessage.__id = id;
|
||||||
|
ws.___listeners.push(onMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
ws._methods.forEach(function (key) {
|
||||||
|
db[key] = function () {
|
||||||
|
rpc(key, Array.prototype.slice.call(arguments));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return db;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.create = create;
|
||||||
20
cluster.js
Normal file
20
cluster.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var memstore = require('./index');
|
||||||
|
|
||||||
|
function create(opts) {
|
||||||
|
var cluster = require('cluster');
|
||||||
|
var numCores = require('os').cpus().length;
|
||||||
|
|
||||||
|
if (!opts.serve && ('boolean' !== typeof opts.serve)) {
|
||||||
|
opts.serve = (numCores > 1) && cluster.isMaster;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opts.connect && ('boolean' !== typeof opts.connect)) {
|
||||||
|
opts.connect = (numCores > 1) && cluster.isWorker;
|
||||||
|
}
|
||||||
|
|
||||||
|
return memstore.create(opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.create = create;
|
||||||
8
index.js
8
index.js
@ -1,9 +1,3 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var cluster = require('cluster');
|
module.exports = require('./client');
|
||||||
|
|
||||||
if (cluster.isMaster) {
|
|
||||||
module.exports = require('./master');
|
|
||||||
} else {
|
|
||||||
module.exports = require('./worker');
|
|
||||||
}
|
|
||||||
|
|||||||
19
master.js
19
master.js
@ -1,19 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports.create = function (opts) {
|
|
||||||
opts = opts || {};
|
|
||||||
|
|
||||||
var db = require('./memstore').create();
|
|
||||||
|
|
||||||
return require('cluster-rpc/master').create({
|
|
||||||
instance: opts.store || db
|
|
||||||
, methods: [
|
|
||||||
'set', 'get', 'touch', 'destroy'
|
|
||||||
, 'all', 'length', 'clear'
|
|
||||||
, 'on', 'off', 'removeEventListener', 'addEventListener'
|
|
||||||
]
|
|
||||||
, name: 'memstore.' + (opts.name || '')
|
|
||||||
, master: opts.master
|
|
||||||
, addOnFork: opts.addOnFork
|
|
||||||
});
|
|
||||||
};
|
|
||||||
19
memstore.js
19
memstore.js
@ -9,30 +9,19 @@ if ('function' === typeof setImmediate) {
|
|||||||
defer = function (fn) { process.nextTick(fn.bind.apply(fn, arguments)); };
|
defer = function (fn) { process.nextTick(fn.bind.apply(fn, arguments)); };
|
||||||
}
|
}
|
||||||
|
|
||||||
function create(opts) {
|
function create(/*opts*/) {
|
||||||
opts = opts || {};
|
|
||||||
// don't leak prototypes as implicitly non-null
|
// don't leak prototypes as implicitly non-null
|
||||||
var db = Object.create(null);
|
var db = Object.create(null);
|
||||||
|
|
||||||
function log() {
|
return Promise.resolve({
|
||||||
if (!opts.debug) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log.apply(console, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
// required / recommended
|
// required / recommended
|
||||||
set: function (id, data, fn) {
|
set: function (id, data, fn) {
|
||||||
log('set(id, data)', id, data);
|
|
||||||
db[id] = data;
|
db[id] = data;
|
||||||
|
|
||||||
if (!fn) { return; }
|
if (!fn) { return; }
|
||||||
defer(fn, null);
|
defer(fn, null);
|
||||||
}
|
}
|
||||||
, get: function (id, fn) {
|
, get: function (id, fn) {
|
||||||
log('get(id)', id);
|
|
||||||
if (!fn) { return; }
|
if (!fn) { return; }
|
||||||
defer(fn, null, 'undefined' === typeof db[id] ? null : db[id]);
|
defer(fn, null, 'undefined' === typeof db[id] ? null : db[id]);
|
||||||
}
|
}
|
||||||
@ -43,7 +32,6 @@ function create(opts) {
|
|||||||
defer(fn, null);
|
defer(fn, null);
|
||||||
}
|
}
|
||||||
, destroy: function (id, fn) {
|
, destroy: function (id, fn) {
|
||||||
log('destroy(id)', id);
|
|
||||||
delete db[id];
|
delete db[id];
|
||||||
|
|
||||||
if (!fn) { return; }
|
if (!fn) { return; }
|
||||||
@ -61,13 +49,12 @@ function create(opts) {
|
|||||||
defer(fn, null, Object.keys(db).length);
|
defer(fn, null, Object.keys(db).length);
|
||||||
}
|
}
|
||||||
, clear: function (fn) {
|
, clear: function (fn) {
|
||||||
log('clear()', id);
|
|
||||||
db = Object.create(null);
|
db = Object.create(null);
|
||||||
|
|
||||||
if (!fn) { return; }
|
if (!fn) { return; }
|
||||||
defer(fn, null);
|
defer(fn, null);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.create = create;
|
module.exports.create = create;
|
||||||
|
|||||||
20
package.json
20
package.json
@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "cluster-store",
|
"name": "cluster-store",
|
||||||
"version": "2.0.8",
|
"version": "1.0.3",
|
||||||
"description": "A wrapper to enable the use of any in-process store with node cluster via cluster process and worker messages (i.e. for Raspberry Pi servers).",
|
"description": "A wrapper to enable the use of a in-process store with node cluster via a socket server (i.e. for Raspberry Pi 2).",
|
||||||
"homepage": "https://git.coolaj86.com/coolaj86/cluster-store.js",
|
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node test-cluster.js",
|
"test": "node test-cluster.js",
|
||||||
@ -10,10 +9,8 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://git.coolaj86.com/coolaj86/cluster-store.js.git"
|
"url": "git+https://github.com/coolaj86/cluster-store.git"
|
||||||
},
|
},
|
||||||
"bundleDependencies": false,
|
|
||||||
"deprecated": false,
|
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"store",
|
"store",
|
||||||
"session",
|
"session",
|
||||||
@ -23,16 +20,13 @@
|
|||||||
"cluster",
|
"cluster",
|
||||||
"rpi2"
|
"rpi2"
|
||||||
],
|
],
|
||||||
"author": {
|
"author": "AJ ONeal <coolaj86@gmail.com> (http://coolaj86.com/)",
|
||||||
"name": "AJ ONeal",
|
|
||||||
"email": "coolaj86@gmail.com",
|
|
||||||
"url": "https://coolaj86.com/"
|
|
||||||
},
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://git.coolaj86.com/coolaj86/cluster-store.js/issues"
|
"url": "https://github.com/coolaj86/cluster-store/issues"
|
||||||
},
|
},
|
||||||
|
"homepage": "https://github.com/coolaj86/cluster-store#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cluster-rpc": "^v1.0.6"
|
"ws": "^0.8.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
150
server.js
Normal file
150
server.js
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
'use strict';
|
||||||
|
/*global Promise*/
|
||||||
|
|
||||||
|
var wsses = {};
|
||||||
|
|
||||||
|
function createApp(server, options) {
|
||||||
|
var promise;
|
||||||
|
|
||||||
|
if (wsses[options.filename]) {
|
||||||
|
return Promise.resolve(wsses[options.filename]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.store) {
|
||||||
|
promise = Promise.resolve(options.store);
|
||||||
|
} else {
|
||||||
|
promise = require('./memstore').create(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise.then(function (db) {
|
||||||
|
var url = require('url');
|
||||||
|
//var express = require('express');
|
||||||
|
//var app = express();
|
||||||
|
var wss = server.wss;
|
||||||
|
|
||||||
|
function app(req, res) {
|
||||||
|
res.end('NOT IMPLEMENTED');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMethods(db) {
|
||||||
|
/*
|
||||||
|
var instanceMethods = Object.keys(db)
|
||||||
|
.map(function (key) { return 'function' === typeof db[key] ? key : null; })
|
||||||
|
.filter(function (key) { return key; })
|
||||||
|
;
|
||||||
|
|
||||||
|
var protoMethods = Object.keys(Object.getPrototypeOf(db))
|
||||||
|
.map(function (key) { return 'function' === typeof Object.getPrototypeOf(db)[key] ? key : null; })
|
||||||
|
.filter(function (key) { return key; })
|
||||||
|
;
|
||||||
|
|
||||||
|
return instanceMethods.concat(protoMethods);
|
||||||
|
*/
|
||||||
|
|
||||||
|
return [
|
||||||
|
'set', 'get', 'touch', 'destroy'
|
||||||
|
, 'all', 'length', 'clear'
|
||||||
|
, 'emit', 'on', 'off', 'once'
|
||||||
|
, 'removeListener', 'addListener'
|
||||||
|
, 'removeEventListener', 'addEventListener'
|
||||||
|
].filter(function (key) {
|
||||||
|
if ('function' === typeof db[key]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
wss.on('connection', function (ws) {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: 'methods'
|
||||||
|
, methods: getMethods(db)
|
||||||
|
}));
|
||||||
|
|
||||||
|
var location = url.parse(ws.upgradeReq.url, true);
|
||||||
|
// you might use location.query.access_token to authenticate or share sessions
|
||||||
|
// or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312
|
||||||
|
|
||||||
|
ws.__session_id = location.query.session_id || require('crypto').randomBytes(16).toString('hex');
|
||||||
|
|
||||||
|
ws.on('message', function (buffer) {
|
||||||
|
var cmd;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cmd = JSON.parse(buffer.toString('utf8'));
|
||||||
|
} catch(e) {
|
||||||
|
console.error('[ERROR] parse json');
|
||||||
|
console.error(e);
|
||||||
|
console.error(buffer);
|
||||||
|
console.error();
|
||||||
|
ws.send(JSON.stringify({ type: 'error', value: { message: e.message, code: "E_PARSE_JSON" } }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(cmd.type) {
|
||||||
|
case 'init':
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'rpc':
|
||||||
|
if (cmd.hasCallback) {
|
||||||
|
cmd.args.push(function () {
|
||||||
|
var args = Array.prototype.slice.call(arguments);
|
||||||
|
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
this: this
|
||||||
|
, args: args
|
||||||
|
, id: cmd.id
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO handle 'off' by id
|
||||||
|
cmd.args[cmd.args.length - 1].__id = cmd.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
db[cmd.func].apply(db, cmd.args);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error('UNKNOWN TYPE');
|
||||||
|
//break;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.send(JSON.stringify({ type: 'session', value: ws.__session_id }));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.masterClient = db;
|
||||||
|
//wsses[options.filename] = app;
|
||||||
|
|
||||||
|
return app;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function create(options) {
|
||||||
|
var server = require('http').createServer();
|
||||||
|
var WebSocketServer = require('ws').Server;
|
||||||
|
var wss = new WebSocketServer({ server: server });
|
||||||
|
//var port = process.env.PORT || process.argv[0] || 4080;
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var ps = [];
|
||||||
|
|
||||||
|
ps.push(new Promise(function (resolve) {
|
||||||
|
fs.unlink(options.sock, function () {
|
||||||
|
// ignore error when socket doesn't exist
|
||||||
|
|
||||||
|
server.listen(options.sock, resolve);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
ps.push(createApp({ server: server, wss: wss }, options).then(function (app) {
|
||||||
|
server.on('request', app);
|
||||||
|
return { masterClient: app.masterClient };
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Promise.all(ps).then(function (results) {
|
||||||
|
return results[1];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.create = create;
|
||||||
26
simplest.js
26
simplest.js
@ -1,26 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var cluster = require('cluster');
|
|
||||||
var cstore;
|
|
||||||
|
|
||||||
require('./').create({
|
|
||||||
name: 'foo-store'
|
|
||||||
}).then(function (store) {
|
|
||||||
if (cluster.isMaster) {
|
|
||||||
cluster.fork();
|
|
||||||
cluster.fork();
|
|
||||||
|
|
||||||
store.set('foo', 'bar');
|
|
||||||
}
|
|
||||||
|
|
||||||
store.get('foo', function (err, result) {
|
|
||||||
console.log(cluster.isMaster && '0' || cluster.worker.id.toString(), 'foo', result);
|
|
||||||
if (!cluster.isMaster) {
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('unhandledRejection', function (err) {
|
|
||||||
console.log('unhandledRejection', err);
|
|
||||||
});
|
|
||||||
15
standalone.js
Normal file
15
standalone.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var memstore = require('./index');
|
||||||
|
|
||||||
|
function create(opts) {
|
||||||
|
opts.standalone = true;
|
||||||
|
|
||||||
|
// TODO if cluster *is* used issue a warning?
|
||||||
|
// I suppose the user could be issuing a different filename for each
|
||||||
|
// ... but then they have no need to use this module, right?
|
||||||
|
|
||||||
|
return memstore.create(opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.create = create;
|
||||||
67
test-cluster.js
Normal file
67
test-cluster.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var cluster = require('cluster');
|
||||||
|
var numCores = 2;
|
||||||
|
//var numCores = require('os').cpus().length;
|
||||||
|
var id = (cluster.isMaster && '0' || cluster.worker.id).toString();
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
var mstore = require('./cluster');
|
||||||
|
|
||||||
|
return mstore.create({
|
||||||
|
standalone: null
|
||||||
|
, serve: null
|
||||||
|
, connect: null
|
||||||
|
}).then(function (store) {
|
||||||
|
store.set('foo', 'bar', function (err) {
|
||||||
|
if (err) { console.error(err); return; }
|
||||||
|
|
||||||
|
store.get('baz', function (err, data) {
|
||||||
|
if (err) { console.error(err); return; }
|
||||||
|
if (null !== data) {
|
||||||
|
console.error(id, 'should be null:', data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
store.get('foo', function (err, data) {
|
||||||
|
if (err) { console.error(err); return; }
|
||||||
|
if ('bar' !== data) {
|
||||||
|
console.error(id, 'should be bar:', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
store.set('quux', { message: 'hey' }, function (/*err*/) {
|
||||||
|
store.get('quux', function (err, data) {
|
||||||
|
if (err) { console.error(err); return; }
|
||||||
|
if (!data || 'hey' !== data.message) {
|
||||||
|
console.error(id, "should be { message: 'hey' }:", data);
|
||||||
|
} else {
|
||||||
|
console.log('Are there any errors above? If not, we passed!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cluster.isMaster) {
|
||||||
|
// not a bad idea to setup the master before forking the workers
|
||||||
|
run().then(function () {
|
||||||
|
var i;
|
||||||
|
|
||||||
|
for (i = 1; i <= numCores; i += 1) {
|
||||||
|
cluster.fork();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The native Promise implementation ignores errors because... dumbness???
|
||||||
|
process.on('unhandledPromiseRejection', function (err) {
|
||||||
|
console.error('Unhandled Promise Rejection');
|
||||||
|
console.error(err);
|
||||||
|
console.error(err.stack);
|
||||||
|
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
37
test-standalone.js
Normal file
37
test-standalone.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
var mstore = require('./standalone');
|
||||||
|
|
||||||
|
mstore.create({
|
||||||
|
sock: '/tmp/memstore.sock'
|
||||||
|
, standalone: null
|
||||||
|
, serve: null
|
||||||
|
, connect: null
|
||||||
|
}).then(function (store) {
|
||||||
|
store.set('foo', 'bar', function (err) {
|
||||||
|
if (err) { console.error(err); return; }
|
||||||
|
|
||||||
|
store.get('baz', function (err, data) {
|
||||||
|
if (err) { console.error(err); return; }
|
||||||
|
console.log('should be null:', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
store.get('foo', function (err, data) {
|
||||||
|
if (err) { console.error(err); return; }
|
||||||
|
console.log('should be bar:', data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
|
||||||
|
// The native Promise implementation ignores errors because... dumbness???
|
||||||
|
process.on('unhandledPromiseRejection', function (err) {
|
||||||
|
console.error('Unhandled Promise Rejection');
|
||||||
|
console.error(err);
|
||||||
|
console.error(err.stack);
|
||||||
|
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
48
test.js
48
test.js
@ -1,48 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var cluster = require('cluster');
|
|
||||||
var cstore;
|
|
||||||
//global.Promise = require('bluebird');
|
|
||||||
|
|
||||||
|
|
||||||
if (cluster.isMaster) {
|
|
||||||
|
|
||||||
|
|
||||||
cstore = require('./master').create({
|
|
||||||
name: 'foo-level'
|
|
||||||
});
|
|
||||||
cstore.then(function (db) {
|
|
||||||
db.set('foo', 'bar');
|
|
||||||
});
|
|
||||||
|
|
||||||
cluster.fork();
|
|
||||||
cluster.fork();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
|
|
||||||
cstore = require('./worker').create({
|
|
||||||
name: 'foo-level'
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
cstore.then(function (db) {
|
|
||||||
setTimeout(function () {
|
|
||||||
db.get('foo', function (err, result) {
|
|
||||||
console.log(cluster.isMaster && '0' || cluster.worker.id.toString(), "db.get('foo')", result);
|
|
||||||
|
|
||||||
if (!cluster.isMaster) {
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 250);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('unhandledRejection', function (err) {
|
|
||||||
console.log('unhandledRejection', err);
|
|
||||||
});
|
|
||||||
Loading…
x
Reference in New Issue
Block a user