Como posso garantir que um trabalho não seja executado duas vezes no Bull?

11

Eu tenho duas funções scheduleScan()e scan().

scan()chamadas scheduleScan() quando não há mais nada a fazer, exceto agendar uma nova verificação , para que scheduleScan()possa agendar a scan(). Mas há um problema, alguns trabalhos são executados duas vezes.

Quero garantir que apenas um trabalho esteja sendo processado a qualquer momento. Como posso conseguir isso? Acredito que tenha algo a ver com done()(estava em scan (), removido agora), mas não consegui encontrar uma solução.

Versão Bull: 3.12.1

Edição tardia importante: scan() chama outras funções e elas podem ou não chamar outras funções, mas todas são funções de sincronização; portanto, elas só chamam uma função quando seus próprios trabalhos são concluídos; existe apenas um caminho a seguir. No final da "árvore", eu chamo, a última função chama scheduleScan (), mas não pode haver dois trabalhos simultâneos em execução. Todo trabalho começa às scan(), a propósito, e termina comscheduleScan(stock, period, milliseconds, 'called by file.js')

export function update(job) {
  // does some calculations, then it may call scheduleScan() or
  // it may call another function, and that could be the one calling
  // scheduleScan() function.
  // For instance, a function like finalize()
}

export function scan(job) {
  update(job)
}


import moment from 'moment'
import stringHash from 'string-hash'
const opts = { redis: { port: 6379, host: '127.0.0.1', password: mypassword' } }
let queue = new Queue('scan', opts)

queue.process(1, (job) => {
  job.progress(100).then(() => {
    scan(job)
  })
})

export function scheduleScan (stock, period, milliseconds, triggeredBy) {
  let uniqueId = stringHash(stock + ':' + period)

  queue.getJob(uniqueId).then(job => {
    if (!job) {
      if (milliseconds) {
        queue.add({ stock, period, triggeredBy }, { delay: milliseconds, jobId: uniqueId }).then(() => {
          // console.log('Added with ms: ' + stock + ' ' + period)
        }).catch(err => {
          if (err) {
            console.log('Can not add because it exists ' + new Date())
          }
        })
      } else {
        queue.add({ stock, period, triggeredBy }, { jobId: uniqueId }).then(() => {
          // console.log('Added without ms: ' + stock + ' ' + period)
        }).catch(err => {
          if (err) {
            console.log('Can not add because it exists ' + new Date())
          }
        })
      }
    } else {
      job.getState().then(state => {
        if (state === 'completed') {
          job.remove().then(() => {
            if (milliseconds) {
              queue.add({ stock, period, triggeredBy }, { delay: milliseconds, jobId: uniqueId }).then(() => {
                // console.log('Added with ms: ' + stock + ' ' + period)
              }).catch(err => {
                if (err) {
                  console.log('Can not add because it exists ' + new Date())
                }
              })
            } else {
              queue.add({ stock, period, triggeredBy }, { jobId: uniqueId }).then(() => {
                // console.log('Added without ms: ' + stock + ' ' + period)
              }).catch(err => {
                if (err) {
                  console.log('Can not add because it exists ' + new Date())
                }
              })
            }
          }).catch(err => {
            if (err) {
              // console.log(err)
            }
          })
        }
      }).catch(err => {
        // console.log(err)
      })
    }
  })
}
salep
fonte
Não consigo encontrar a scanfunção, você pode ajudar?
Muhammad Zeeshan
@MuhammadZeeshan adicionei, meu erro.
salep 9/02

Respostas:

6

O problema, acredito, é que sua scanfunção é assíncrona. Portanto, sua job.progressfunção chama scane chama imediatamente, donepermitindo que a fila processe outro trabalho.

Uma solução poderia ser a de passar o doneretorno de chamada como um parâmetro para o seu scane scheduleScanfunções, e invocá-lo, depois de ter concluído o seu trabalho (ou em caso de erro).

Outro (melhor) solução poderia ser a de garantir que você sempre voltar a Promisepartir descan e scheduleScanespere a promessa de resolver e depois ligar done. Ao fazer isso, certifique-se de encadear todos os retornos de sua promessa em sua scheduleScanfunção.

queue.process(1, (job, done) => {
  job.progress(100).then(() => {
    scan(job)
        .then(done)
        .catch(done)
  })
})

export function scan() {
   // business logic
   return scheduleScan()
}

// Chain all of your promise returns. Otherwise
// the scan function will return sooner and allow done to be called
// prior to the scheduleScan function finishing it's execution
export function scheduleScan() {
    return queue.getJob(..).then(() => {
        ....
        return queue.add()...
        ....
        return queue.add(...)
            .catch(e => {
                 console.log(e);
                 // propogate errors!
                 throw e;
             })

}
jeeves
fonte
Eu editei minha pergunta. Você pode consultá-la novamente, especialmente a parte "Edição tardia importante"? Sua resposta ainda se aplica a esta situação? Obrigado.
salep 9/02
11
Sim, ainda é válido. Na sua edição, acho que você está dizendo que scheduledScansempre é chamado depois de todas as outras funções de sincronização scan. Se for esse o caso, então sim, minha resposta ainda é válida. Sempre retorne sempre a promessa que será retornada scheduleScanna scanfunção
jeeves 09/02
Mais uma vez, meu erro. A primeira função, update (), está em varredura, mas update () pode chamar outra função como finalize () e finalize () pode chamar scheduleScan (). Lembre-se de que isso acontece em um pedido, portanto, não há várias chamadas. Estou fazendo isso para manter meu aplicativo modular. - Obrigado
salep
11
Sim, a mesma resposta. Se updatechamadas scheduledScanou qualquer número de funções entre eles. O ponto principal é que você precisa retornar a cadeia de promessas de scheduleScanvolta à scanfunção. Portanto, se as scanchamadas updateque chamam finalise..... As chamadas scheduleScanda cadeia de promessas precisarão ser retornadas através de todas as chamadas de função, ou seja, apenas certifique-se de retornar a promessa de cada uma dessas funções.
jeeves 9/02
Então, só para esclarecer meu último comentário. Por exemplo, se dentro da verificação você chamar atualização. Você precisa retornar o resultado da atualização (uma promessa) da função de digitalização.
jeeves 09/02
4

A função de verificação é uma função assíncrona. Na sua queue.process()função, é necessário aguardar a função de digitalização e, em seguida, chamar o done()retorno de chamada.

export async function scan(job) {
  // it does some calculations, then it creates a new schedule.
  return scheduleScan(stock, period, milliseconds, "scan.js");
}

queue.process(1, (job, done) => {
  job.progress(100).then(async() => {
    await scan(job);
    done();
  });
});

export async function scheduleScan(stock, period, milliseconds, triggeredBy) {
    let uniqueId = stringHash(stock + ":" + period);
    try {
      const existingJob = await queue.getJob(uniqueId);
      if (!existingJob) {
        const job = await addJob({
          queue,
          stock,
          period,
          uniqueId,
          milliseconds,
          triggeredBy
        });
        return job;
      } else {
        const jobState = await existingJob.getState();
        if (jobState === "completed") {
          await existingJob.remove();
          const newJob = await addJob({
            queue,
            stock,
            period,
            uniqueId,
            milliseconds,
            triggeredBy
          });
          return newJob;
        }
      }
    } catch (err) {
      throw new Error(err);
    }
}

export function addJob({ queue, stock, period, milliseconds, triggeredBy }) {
  if (milliseconds) {
    return queue.add(
      { stock, period, triggeredBy },
      { delay: milliseconds, jobId: uniqueId }
    );
  } else {
    return queue.add({ stock, period, triggeredBy }, { jobId: uniqueId });
  }
}

Tente isso! Eu tentei refatorar o código um pouco usando async-waitit.

Adithya Sreyaj
fonte
Eu editei minha pergunta. Você pode consultá-la novamente, especialmente a parte "Edição tardia importante"? Sua resposta ainda se aplica a esta situação? Obrigado.
salep 9/02