Compare commits
No commits in common. "v1.0.3" and "master" have entirely different histories.
14
.gitignore
vendored
14
.gitignore
vendored
@ -1,6 +1,7 @@
|
|||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
pids
|
pids
|
||||||
@ -13,6 +14,9 @@ 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
|
||||||
|
|
||||||
@ -22,6 +26,12 @@ coverage
|
|||||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
build/Release
|
build/Release
|
||||||
|
|
||||||
# Dependency directory
|
# Dependency directories
|
||||||
# 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,202 +1,21 @@
|
|||||||
Apache License
|
MIT License
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
Copyright (c) 2016 AJ ONeal
|
||||||
|
|
||||||
1. Definitions.
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
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:
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
The above copyright notice and this permission notice shall be included in all
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
"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.
|
||||||
|
|||||||
170
README.md
170
README.md
@ -1,73 +1,103 @@
|
|||||||
Cluster Store
|
cluster-store
|
||||||
=============
|
=============
|
||||||
|
|
||||||
A very simple in-memory object store for use with node cluster
|
Makes any storage strategy similar to `express/session` useful in both `cluster` and non-`cluster` environments
|
||||||
(or even completely and unrelated node processes).
|
by wrapping it with `cluster-rpc`.
|
||||||
|
|
||||||
Node.js runs on a single core, which isn't very effective.
|
Also works with **level-session-store** (leveldb), **connect-session-knex** (SQLite3), **session-file-store** (fs),
|
||||||
|
and any other embedded / in-process store.
|
||||||
|
|
||||||
You can run multiple Node.js instances to take advantage of multiple cores,
|
Note: Most people would probably prefer to just use Redis rather than wrap a dumb memstore as a service...
|
||||||
but if you do that, you can't share memory between processes.
|
but I am not most people.
|
||||||
|
|
||||||
This module will either run client-server style in environments that benefit from it
|
Install
|
||||||
(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
|
```
|
||||||
wrap a dumb memstore as a service... but I am not most people.
|
npm install --save cluster-store@2.x
|
||||||
|
```
|
||||||
|
|
||||||
Also works with **level-session-store** (leveldb), **connect-session-knex** (SQLite3),
|
v1.x vs v2.x
|
||||||
**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
|
||||||
=====
|
=====
|
||||||
|
|
||||||
The default behavior is to try to connect to a master and, if that fails, to become the master.
|
In its simplest form, you use this module nearly exactly the way you would
|
||||||
|
the any other storage module, with the exception that you must wait for
|
||||||
|
the inter-process initialization to complete.
|
||||||
|
|
||||||
However, if you are in fact using the `cluster` rather than spinning up random instances,
|
When not using any of the options the usage is the same for the master and the worker:
|
||||||
you'll probably prefer to use this pattern:
|
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
var cluster = require('cluster');
|
require('cluster-store').create().then(function (store) {
|
||||||
var cstore = require('cluster-store');
|
// initialization is now complete
|
||||||
var numCores = require('os').cpus().length;
|
store.set('foo', 'bar');
|
||||||
|
|
||||||
var opts = {
|
|
||||||
sock: '/tmp/memstore.sock'
|
|
||||||
|
|
||||||
// If left 'null' or 'undefined' this defaults to a similar memstore
|
|
||||||
// with no special logic for 'cookie' or 'expires'
|
|
||||||
, store: cluster.isMaster && new require('express-session/session/memory')()
|
|
||||||
|
|
||||||
// a good default to use for instances where you might want
|
|
||||||
// to cluster or to run standalone, but with the same API
|
|
||||||
, serve: cluster.isMaster
|
|
||||||
, connect: cluster.isWorker
|
|
||||||
, standalone: (1 === numCores) // overrides serve and connect
|
|
||||||
};
|
|
||||||
|
|
||||||
cstore.create(opts).then(function (store) {
|
|
||||||
// same api as new new require('express-session/session/memory')(
|
|
||||||
|
|
||||||
store.get(id, function (err, data) {
|
|
||||||
console.log(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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`.
|
### standalone (non-cluster)
|
||||||
|
--------------
|
||||||
|
|
||||||
Likewise, if you wish to use standalone mode in a particular worker process see `test-standalone.js`.
|
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';
|
||||||
|
|
||||||
|
var cluster = require('cluster');
|
||||||
|
|
||||||
|
var cstore = require('cluster-store/master').create({
|
||||||
|
name: 'foo-store' // necessary when using multiple instances
|
||||||
|
, store: null // use default in-memory store
|
||||||
|
, addOnFork: true // default
|
||||||
|
});
|
||||||
|
|
||||||
|
// if you addOnFork is set to false you can add specific forks manually
|
||||||
|
//cstore.addWorker(cluster.fork());
|
||||||
|
|
||||||
|
cstore.then(function (store) {
|
||||||
|
store.set('foo', 'bar');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: `store` can be replaced with any `express/session`-compatible store, such as:
|
||||||
|
|
||||||
|
* `new require('express-session/session/memory')()`
|
||||||
|
* `require('level-session-store')(session)`
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
API
|
API
|
||||||
===
|
===
|
||||||
@ -94,22 +124,28 @@ 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
|
||||||
|
|
||||||
Standalone / Master Mode is in-process
|
Example
|
||||||
========================
|
=======
|
||||||
|
|
||||||
The `master` in the cluster (meaning `opts.serve = true`) will directly hold the specified store
|
```javascript
|
||||||
(a simple memory store by default, or `express-session/session/memory` in the example above)
|
'use strict';
|
||||||
|
|
||||||
Likewise, when only one process is being used (`opts.standalone = true`) the listener is
|
var cluster = require('cluster');
|
||||||
not started and API is completely in-process.
|
|
||||||
|
|
||||||
If you take a look at `memstore.js` you'll see that it's a rather simple memory store instance.
|
require('cluster-store').create({
|
||||||
|
name: 'foo-store'
|
||||||
|
}).then(function (store) {
|
||||||
|
if (cluster.isMaster) {
|
||||||
|
store.set('foo', 'bar');
|
||||||
|
}
|
||||||
|
|
||||||
Security Warning
|
store.get('foo', function (err, result) {
|
||||||
================
|
console.log(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Note that any application on the system could connect to the socket.
|
if (cluster.isMaster) {
|
||||||
|
cluster.fork();
|
||||||
In the future I may add a `secret` field in the options object to be
|
cluster.fork();
|
||||||
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
199
client.js
@ -1,199 +0,0 @@
|
|||||||
'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
20
cluster.js
@ -1,20 +0,0 @@
|
|||||||
'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,3 +1,9 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
module.exports = require('./client');
|
var cluster = require('cluster');
|
||||||
|
|
||||||
|
if (cluster.isMaster) {
|
||||||
|
module.exports = require('./master');
|
||||||
|
} else {
|
||||||
|
module.exports = require('./worker');
|
||||||
|
}
|
||||||
|
|||||||
19
master.js
Normal file
19
master.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
'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,19 +9,30 @@ 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);
|
||||||
|
|
||||||
return Promise.resolve({
|
function log() {
|
||||||
|
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]);
|
||||||
}
|
}
|
||||||
@ -32,6 +43,7 @@ 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; }
|
||||||
@ -49,12 +61,13 @@ 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,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "cluster-store",
|
"name": "cluster-store",
|
||||||
"version": "1.0.3",
|
"version": "2.0.8",
|
||||||
"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).",
|
"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).",
|
||||||
|
"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",
|
||||||
@ -9,8 +10,10 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/coolaj86/cluster-store.git"
|
"url": "git+https://git.coolaj86.com/coolaj86/cluster-store.js.git"
|
||||||
},
|
},
|
||||||
|
"bundleDependencies": false,
|
||||||
|
"deprecated": false,
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"store",
|
"store",
|
||||||
"session",
|
"session",
|
||||||
@ -20,13 +23,16 @@
|
|||||||
"cluster",
|
"cluster",
|
||||||
"rpi2"
|
"rpi2"
|
||||||
],
|
],
|
||||||
"author": "AJ ONeal <coolaj86@gmail.com> (http://coolaj86.com/)",
|
"author": {
|
||||||
|
"name": "AJ ONeal",
|
||||||
|
"email": "coolaj86@gmail.com",
|
||||||
|
"url": "https://coolaj86.com/"
|
||||||
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/coolaj86/cluster-store/issues"
|
"url": "https://git.coolaj86.com/coolaj86/cluster-store.js/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/coolaj86/cluster-store#readme",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ws": "^0.8.0"
|
"cluster-rpc": "^v1.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
150
server.js
150
server.js
@ -1,150 +0,0 @@
|
|||||||
'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
Normal file
26
simplest.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
'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);
|
||||||
|
});
|
||||||
@ -1,15 +0,0 @@
|
|||||||
'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;
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
'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);
|
|
||||||
});
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
'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
Normal file
48
test.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
'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