jobsPesados.js 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. import { config } from "../config/index.js";
  2. import { descarregarModelo } from "../services/ollamaClient.js";
  3. import { partesLocaisSaoPaulo } from "./timezone.js";
  4. // jobs que usam o Ollama em lote (avaliação, ingest RAG) — só um por vez, pra não somar
  5. // memória/GPU e travar a máquina
  6. let jobPesadoAtual = null;
  7. // janela "HH-HH" no fuso de São Paulo (ex.: "19-07" atravessa a meia-noite); vazia = sempre
  8. export function dentroDaJanela(date = new Date()) {
  9. const janela = config.jobsPesados.janela;
  10. if (!janela) return true;
  11. const m = /^(\d{1,2})-(\d{1,2})$/.exec(janela);
  12. if (!m) return true;
  13. const inicio = Number(m[1]);
  14. const fim = Number(m[2]);
  15. const hora = Number(partesLocaisSaoPaulo(date, { hour: "numeric", hourCycle: "h23" }).hour);
  16. if (inicio === fim) return true;
  17. return inicio < fim ? hora >= inicio && hora < fim : hora >= inicio || hora < fim;
  18. }
  19. export function pausaEntreItens() {
  20. const ms = config.jobsPesados.pausaMs;
  21. return ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
  22. }
  23. // roda `fn` se nenhum outro job pesado estiver rodando e se estiver dentro da janela;
  24. // caso contrário devolve null (o ciclo é só pulado, o próximo tenta de novo)
  25. export async function comLockPesado(nome, fn) {
  26. if (!dentroDaJanela()) {
  27. console.log(`[${nome}] fora da janela JOBS_PESADOS_JANELA=${config.jobsPesados.janela}, pulando`);
  28. return null;
  29. }
  30. if (jobPesadoAtual) {
  31. console.log(`[${nome}] ocupado por ${jobPesadoAtual}, pulando este ciclo`);
  32. return null;
  33. }
  34. jobPesadoAtual = nome;
  35. try {
  36. return await fn();
  37. } finally {
  38. jobPesadoAtual = null;
  39. }
  40. }
  41. // disparo manual (admin via HTTP): ignora a janela, mas respeita a exclusão mútua com os jobs —
  42. // se houver um job pesado rodando, falha com 409 em vez de somar carga no Ollama
  43. export async function comLockPesadoManual(nome, fn) {
  44. if (jobPesadoAtual) {
  45. // o frontend mostra a mensagem crua — por isso em português
  46. const err = new Error(`Há outro processamento pesado em andamento (${jobPesadoAtual}). Tente de novo em alguns minutos.`);
  47. err.statusCode = 409;
  48. throw err;
  49. }
  50. jobPesadoAtual = nome;
  51. try {
  52. return await fn();
  53. } finally {
  54. jobPesadoAtual = null;
  55. }
  56. }
  57. // best-effort: falha ao descarregar não deve derrubar o job
  58. export async function descarregarModelos(nome, modelos) {
  59. if (!config.jobsPesados.descarregarAoFim) return;
  60. for (const model of new Set(modelos.filter(Boolean))) {
  61. try {
  62. await descarregarModelo(model);
  63. } catch (err) {
  64. console.warn(`[${nome}] falha ao descarregar ${model}:`, err.message);
  65. }
  66. }
  67. }