Repository: OptimalBits/bull (JavaScript)

Issue:
Cannot do job.update when job runs in separate process
## Description
I have changed the queue processor from a normal process to a **separate process** and now I cannot update a job anymore. 
It throws the following error:
```(node:7068) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: job.update is not a function```

## Minimal, Working Test code to reproduce the issue.
```
// master.js

const Bull = require('bull');
const queue = new Bull("html_pusher", { redis: { "port": port, "host": "ip", "db": 0, "showFriendlyErrorStack": true, "password": "pwd" } });

const proc = require('./child.js');
queue.process(1, proc);
```

```
// child.js
module.exports = proc;

async function proc(job, done) {
  console.log(`HI JOB ${job.id}`);
  console.log(job);
  job.data.foo = 'bar';
  await job.update(job.data);
  return done();
}
```

when I convert the processor call to a **separate process** the `job.update` throws the error
```
// master.js

const Bull = require('bull');
const queue = new Bull("html_pusher", { redis: { "port": port, "host": "ip", "db": 0, "showFriendlyErrorStack": true, "password": "pwd" } });

queue.process(__dirname + "/child.js");
```

## Bull version
3.4.8
## Additional information
This is the content of **job** when processor is within the master process:

```
Job {
  opts:
   { attempts: 1,
     delay: 0,
     timestamp: 1555080724183,
     backoff: undefined },
  name: '__default__',
  queue:
   Queue {
     name: 'html_pusher',
     token: '641dc8a5-7722-4015-a464-06c268685617',
     keyPrefix: 'bull',
     clients: [ [Object], [Object], [Object] ],
     _initializing: Promise { undefined },
     handlers: { __default__: [AsyncFunction: bound proc] },
     processing: [ [Object] ],
     retrieving: 0,
     drained: false,
     settings:
      { lockDuration: 30000,
        stalledInterval: 30000,
        maxStalledCount: 1,
        guardInterval: 5000,
        retryProcessDelay: 5000,
        drainDelay: 5,
        backoffStrategies: {},
        lockRenewTime: 15000 },
     _events: { error: [Function] },
     _eventsCount: 1,
     timers: TimerManager { idle: false, listeners: [], timers: [Object] },
     moveUnlockedJobsToWait: [Function: bound ],
     processJob: [Function: bound ],
     getJobFromId: [Function: bound ],
     keys:
      { '': 'bull:html_pusher:',
        active: 'bull:html_pusher:active',
        wait: 'bull:html_pusher:wait',
        waiting: 'bull:html_pusher:waiting',
        paused: 'bull:html_pusher:paused',
        resumed: 'bull:html_pusher:resumed',
        'meta-paused': 'bull:html_pusher:meta-paused',
        id: 'bull:html_pusher:id',
        delayed: 'bull:html_pusher:delayed',
        priority: 'bull:html_pusher:priority',
        'stalled-check': 'bull:html_pusher:stalled-check',
        completed: 'bull:html_pusher:completed',
        failed: 'bull:html_pusher:failed',
        stalled: 'bull:html_pusher:stalled',
        repeat: 'bull:html_pusher:repeat',
        limiter: 'bull:html_pusher:limiter',
        drained: 'bull:html_pusher:drained',
        progress: 'bull:html_pusher:progress' },
     delayedTimestamp: 1.7976931348623157e+308,
     _initializingProcess: Promise { undefined },
     errorRetryTimer: {},
     registeredEvents: { delayed: [Object] },
     guardianTimer:
      Timeout {
        _called: true,
        _idleTimeout: 5000,
        _idlePrev: [Object],
        _idleNext: [Object],
        _idleStart: 30565,
        _onTimeout: [Function],
        _timerArgs: undefined,
        _repeat: 5000,
        _destroyed: false,
        [Symbol(asyncId)]: 116,
        [Symbol(triggerAsyncId)]: 0 },
     moveUnlockedJobsToWaitInterval:
      Timeout {
        _called: true,
        _idleTimeout: 30000,
        _idlePrev: [Object],
        _idleNext: [Object],
        _idleStart: 30650,
        _onTimeout: [Function: bound ],
        _timerArgs: undefined,
        _repeat: 30000,
        _destroyed: false,
        [Symbol(asyncId)]: 135,
        [Symbol(triggerAsyncId)]: 0 } },
  data:
   {
... my data ...
},
  _progress: 0,
  delay: 0,
  timestamp: 1555080724183,
  stacktrace: [],
  returnvalue: null,
  attemptsMade: 0,
  toKey: [Function: wrapper],
  id: '6604614',
  processedOn: 1555080725310,
  failedReason: undefined }
```

This is the content of **job** when job runs in a separate process:
```
{ id: '6604006',
  name: '__default__',
  data:
   { 
.... my data ....
  },
opts: { attempts: 1, delay: 0, timestamp: 1555080466092 },
progress: [Function],
  delay: 0,
  timestamp: 1555080466092,
  attemptsMade: 0,
  stacktrace: [],
  returnvalue: null,
  finishedOn: null,
  processedOn: 1555080467236 }
```

It seems that when the job is in a separate process some properties are missing.


Discussion:
hi @manast  ,
is there a workaround for this issue? do you have time to fix this?

thanks
It is a missing feature. It needs to be implemented I am have a lot on my table right now so I cannot do an ETA right now.
Any update ?
+1 :)
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

:tada: This issue has been resolved in version 3.23.0 :tada:

The release is available on:
- [npm package (@latest dist-tag)](https://www.npmjs.com/package/bull/v/3.23.0)
- [GitHub release](https://github.com/OptimalBits/bull/releases/tag/v3.23.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:



Patch:
diff --git a/lib/process/master.js b/lib/process/master.js
index 9df614c9a..e67565dc3 100644
--- a/lib/process/master.js
+++ b/lib/process/master.js
@@ -140,5 +140,15 @@ function wrapJob(job) {
       value: row
     });
   };
+  /*
+   * Emulate the real job `update` function.
+   */
+  job.update = function(data) {
+    process.send({
+      cmd: 'update',
+      value: data
+    });
+    job.data = data;
+  };
   return job;
 }
diff --git a/lib/process/sandbox.js b/lib/process/sandbox.js
index 1da6721be..745989f75 100644
--- a/lib/process/sandbox.js
+++ b/lib/process/sandbox.js
@@ -33,6 +33,9 @@ module.exports = function(processFile, childPool) {
             case 'log':
               job.log(msg.value);
               break;
+            case 'update':
+              job.update(msg.value);
+              break;
           }
         };
 


Test patch:
diff --git a/test/fixtures/fixture_processor_data.js b/test/fixtures/fixture_processor_data.js
new file mode 100644
index 000000000..1a04ab028
--- /dev/null
+++ b/test/fixtures/fixture_processor_data.js
@@ -0,0 +1,14 @@
+/**
+ * A processor file to be used in tests.
+ *
+ */
+'use strict';
+
+const delay = require('delay');
+
+module.exports = function(job) {
+  return delay(50).then(() => {
+    job.update({ baz: 'qux' });
+    return job.data;
+  });
+};
diff --git a/test/test_sandboxed_process.js b/test/test_sandboxed_process.js
index c0fdbbd66..4deae55b5 100644
--- a/test/test_sandboxed_process.js
+++ b/test/test_sandboxed_process.js
@@ -228,6 +228,24 @@ describe('sandboxed process', () => {
     queue.add({ foo: 'bar' });
   });
 
+  it('should process and update data', done => {
+    queue.process(__dirname + '/fixtures/fixture_processor_data.js');
+
+    queue.on('completed', (job, value) => {
+      try {
+        expect(job.data).to.be.eql({ baz: 'qux' });
+        expect(value).to.be.eql({ baz: 'qux' });
+        expect(Object.keys(queue.childPool.retained)).to.have.lengthOf(0);
+        expect(queue.childPool.getAllFree()).to.have.lengthOf(1);
+        done();
+      } catch (err) {
+        done(err);
+      }
+    });
+
+    queue.add({ foo: 'bar' });
+  });
+
   it('should process and fail', done => {
     queue.process(__dirname + '/fixtures/fixture_processor_fail.js');