@scz/queue-job-worker-pool 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@scz/queue-job-worker-pool",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1",
8
+ "pub": "npm publish --access public --registry=https://registry.npmjs.org"
9
+ },
10
+ "author": "Sunny",
11
+ "license": "ISC",
12
+ "dependencies": {
13
+ "@scz/worker-threads": "^1.0.0",
14
+ "deepmerge": "^4.3.1"
15
+ }
16
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ *
3
+ */
4
+ const JobQueueTool = require('./tools/JobQueueTool');
5
+
6
+ class JobQueue {
7
+ #jobs = [];
8
+ #job_null_count = 0;
9
+ #is_init = true;
10
+
11
+ constructor(options, poolthis) {
12
+ this.#jobs = [];
13
+ this.options = options;
14
+ this.pool = poolthis;
15
+
16
+ this.#is_init = true;
17
+ }
18
+
19
+ addJob(jobData) {
20
+ if (this.#jobs.length >= this.options.jobQueueSize) {
21
+ throw new Error('Job queue is full');
22
+ }
23
+ let job = JobQueueTool.makeJob(jobData);
24
+ this.#jobs.push(job);
25
+
26
+ this.#job_null_count = 0;
27
+ this.#is_init = false;
28
+ }
29
+
30
+ getJob() {
31
+ let job = this.#jobs.shift();
32
+ if (!job && this.#jobs.length === 0 && !this.#is_init) {
33
+ this.#job_null_count++;
34
+ }
35
+ this.#checkJobQueueDrained();
36
+ return job;
37
+ }
38
+
39
+ getJobQueueLength() {
40
+ return this.#jobs.length;
41
+ }
42
+
43
+ #checkJobQueueDrained() {
44
+ if (this.#job_null_count >= this.pool.getWorkerCount()) {
45
+ this.#job_null_count = 0;
46
+ this.pool.emit('job:drained');
47
+ }
48
+ }
49
+ }
50
+ module.exports = JobQueue;
@@ -0,0 +1,139 @@
1
+ /**
2
+ *
3
+ */
4
+ const EventEmitter = require('node:events');
5
+
6
+ const deepmerge = require('deepmerge');
7
+ const threads = require('@scz/worker-threads');
8
+
9
+ const JobQueue = require('./JobQueue');
10
+ const JobQueueTool = require('./tools/JobQueueTool');
11
+
12
+ class JobQueueThreadPool extends EventEmitter {
13
+ #jobs_queue = null;
14
+ #workers = {};
15
+ #workerPath;
16
+ #options;
17
+ #workerOptions = {};
18
+
19
+ constructor(workerPath, options) {
20
+ super();
21
+ let defaultOptions = {
22
+ threadsPoolSize: 4,
23
+ jobQueueSize: 1000,
24
+ workerOptions: {
25
+ workerData: {},
26
+ //如果你要给a.js传递参数,要使用此选项。
27
+ //请参考官方文档对选项的描述。
28
+ argv: [],
29
+ // restart: 'fail',
30
+ // restartDelay: 1000,
31
+
32
+ }
33
+ };
34
+ this.#workerPath = workerPath;
35
+ this.#options = deepmerge(defaultOptions, options);
36
+ if (options.workerOptions && options.workerOptions.events) {
37
+ delete options.workerOptions.events;
38
+ }
39
+ this.#workerOptions = deepmerge(this.#workerOptions, this.#options.workerOptions);
40
+
41
+ this.#jobs_queue = new JobQueue(this.#options, this);
42
+ this.#createThreadPool();
43
+ }
44
+
45
+ #createThread() {
46
+ let self = this;
47
+ let worker = threads(this.#workerPath, {
48
+ ...this.#workerOptions,
49
+ events: {
50
+ message: async (w, msg) => {
51
+ switch (msg.type) {
52
+ case 'job:completed':
53
+ self.emit('job:completed', {
54
+ worker: w,
55
+ job: msg.job,
56
+ });
57
+ break;
58
+
59
+ case 'job:failed':
60
+ self.emit('job:failed', {
61
+ worker: w,
62
+ job: msg.job,
63
+ error: msg.error,
64
+ });
65
+ break;
66
+
67
+ default:
68
+ break;
69
+ }
70
+
71
+ let job = self.#getJob();
72
+ if (job) {
73
+ self.#sendJobToWorker(w, job);
74
+ } else {
75
+ w._status = 'waiting';
76
+ }
77
+ },
78
+ 'online': (w) => {
79
+ let job2 = self.#getJob();
80
+ if (job2) {
81
+ w._status = 'running';
82
+ self.#sendJobToWorker(w, job2);
83
+ }
84
+ self.emit('worker:online', w);
85
+ },
86
+ 'error': (w, error) => {
87
+ self.emit('worker:error', { worker: w, error });
88
+ },
89
+ // w就是worker,扩展做了包装处理,
90
+ // 这样方便拿到worker对象。
91
+ exit: (w, code) => {
92
+ self.emit('worker:exit', { worker: w, code });
93
+ delete this.#workers[w.threadId];
94
+ },
95
+ },
96
+ });
97
+
98
+ this.#workers[worker.threadId] = worker;
99
+ this.#workers[worker.threadId]._status = 'waiting';
100
+ }
101
+
102
+ #createThreadPool() {
103
+ for (let i = 0; i < this.#options.threadsPoolSize; i++) {
104
+ this.#createThread();
105
+ }
106
+ }
107
+
108
+ #sendJobToWorker(worker, job) {
109
+ worker.postMessage({
110
+ type: 'job:data',
111
+ job,
112
+ });
113
+ }
114
+
115
+ #getJob() {
116
+ return this.#jobs_queue.getJob();
117
+ }
118
+
119
+ addJob(jobData) {
120
+ this.#jobs_queue.addJob(jobData);
121
+
122
+ let w_keys = Object.keys(this.#workers);
123
+ w: for (const key of w_keys) {
124
+ let w = this.#workers[key];
125
+ if (w._status === 'waiting') {
126
+ let job = this.#getJob();
127
+ this.#sendJobToWorker(w, job);
128
+ w._status = 'running';
129
+ return;
130
+ }
131
+ }
132
+ }
133
+
134
+ getWorkerCount() {
135
+ return Object.keys(this.#workers).length;
136
+ }
137
+ }
138
+
139
+ module.exports = JobQueueThreadPool;
@@ -0,0 +1,52 @@
1
+ /**
2
+ *
3
+ */
4
+
5
+ const EventEmitter = require('node:events');
6
+ const { parentPort } = require('node:worker_threads');
7
+
8
+
9
+
10
+ class WorkerThreadControl extends EventEmitter {
11
+ constructor() {
12
+ super();
13
+ }
14
+
15
+ sendJobCompleted(job) {
16
+ parentPort.postMessage({
17
+ type: 'job:completed',
18
+ job,
19
+ });
20
+ }
21
+
22
+ sendJobFailed(job, error) {
23
+ parentPort.postMessage({
24
+ type: 'job:failed',
25
+ job,
26
+ error,
27
+ });
28
+ }
29
+ }
30
+
31
+ let wtc = null;
32
+
33
+
34
+ module.exports = function () {
35
+ if (wtc) {
36
+ return wtc;
37
+ }
38
+ wtc = new WorkerThreadControl();
39
+
40
+ parentPort.on('message', (msg) => {
41
+ switch (msg.type) {
42
+ case 'job:data':
43
+ wtc.emit('data', msg.job);
44
+ break;
45
+
46
+ default:
47
+ break;
48
+ }
49
+
50
+ });
51
+ return wtc;
52
+ };
package/src/index.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ *
3
+ */
4
+ const JobQueueThreadPool = require('./JobQueueThreadPool');
5
+ const WorkerThreadControl = require('./WorkerThreadControl');
6
+
7
+ module.exports = {
8
+ JobQueueThreadPool,
9
+ WorkerThreadControl,
10
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ *
3
+ */
4
+ const crypto = require('crypto');
5
+
6
+ function md5(content) {
7
+ const hash = crypto.createHash('md5'); // 创建一个MD5哈希对象
8
+ hash.update(content); // 对输入文本进行更新
9
+ return hash.digest('hex'); // 返回十六进制格式的哈希值
10
+ }
11
+
12
+ function makeJob(jobData) {
13
+ let str = JSON.stringify(jobData);
14
+ let jobId = md5(str);
15
+ return {
16
+ id: jobId,
17
+ data: jobData,
18
+ };
19
+ }
20
+
21
+ module.exports = {
22
+ makeJob,
23
+ };