Skip to Content
PortuguêsDocumentação JumentixPacotes@jumentix/mutex-serviceUso do mutex-service

@jumentix/mutex-service — guia de uso

Responsabilidade no escopo

  • Camada: persistência / coordenação
  • Responsável por: locks nomeados sobre um cliente KV
  • Usado com: @jumentix/key-value-storage
  • Não responsável por: documentos de negócio ou messaging

O que é

@jumentix/mutex-service fornece locks nomeados apoiados em @jumentix/key-value-storage. Dois workers (ou duas requisições) não podem segurar o mesmo id de lock para o mesmo nome de recurso ao mesmo tempo. O serviço expõe lock, isLocked e unlock — todos retornando ServiceResponse.

Por que existe

Sem lock compartilhado, webhooks duplicados, sobreposição de cron ou escritas paralelas em abas podem corromper a mesma fatura, assento ou registro de design. Times júnior precisam de uma API de lock pequena e testável que funcione em memória no desenvolvimento e no Redis em produção — usando a mesma porta KV que o resto da stack já usa.

Pré-requisitos

  • Pacotes: @jumentix/mutex-service e @jumentix/key-value-storage.
  • Runtime: browser (KV InMemory + stub do playground) ou Node/Bun (KV InMemory ou Redis).
  • Leitura prévia: guia key-value-storage.
  • Conceito: chave de lock é {mutexPrefix}:{resourceName}:{uuid} onde uuid identifica esta tentativa (use crypto.randomUUID()).

Glossário

TermoSignificado
Nome do recursoNome lógico do que você protege (ex.: invoice-42, design-sync).
Uuid do lockId único de uma tentativa; armazenado como marcador no KV.
Previously lockedResultado de lock() quando outro holder já possui o recurso.
PrefixoNamespace das chaves de mutex; padrão mutex: (configurável via IMutexServiceOptions).
Seção críticaCódigo entre lock bem-sucedido e unlock — mantenha curta.
ServiceResponse{ result?, error? } — inspecione error antes de confiar em result.

Passos

1. Instalar (< 5 minutos)

bun add @jumentix/mutex-service @jumentix/key-value-storage

2. Primeiro sucesso — adquirir e liberar (< 15 minutos)

import { MutexService } from '@jumentix/mutex-service';
import { InMemoryKeyValueStorageClient } from '@jumentix/key-value-storage';

const kv = InMemoryKeyValueStorageClient.compile();
await kv.connect();

const mutex = MutexService.compile(kv);
const lockId = crypto.randomUUID();
const resource = 'docs-demo';

const acquired = await mutex.lock(resource, lockId);
if (acquired.error) throw acquired.error;

if (!acquired.result?.locked) {
  console.log('Recurso ocupado', acquired.result?.previouslyLocked);
} else {
  try {
    // seção crítica — mantenha pequena
    await doWork(resource);
  } finally {
    await mutex.unlock(resource, lockId);
  }
}

Verifique o sucesso: primeiro lock retorna { locked: true }; após unlock, isLocked retorna { result: false }.

3. Fluxo central — sempre liberar em finally

async function withLock<T>(
  mutex: MutexService,
  resource: string,
  fn: () => Promise<T>
): Promise<T | null> {
  const lockId = crypto.randomUUID();
  const { result, error } = await mutex.lock(resource, lockId);
  if (error) throw error;
  if (!result?.locked) return null;

  try {
    return await fn();
  } finally {
    await mutex.unlock(resource, lockId);
  }
}

Nunca segure lock através de I/O remoto lento a menos que aceite risco de timeout e lock obsoleto.

4. Fluxo central — verificar antes de repetir

const status = await mutex.isLocked(resource, lockId);
if (status.result) {
  // ainda segurado — espere ou mostre "ocupado" ao usuário
}

5. Fluxo central — wiring de produção

import { compileKeyValueStorageClient } from '@jumentix/key-value-storage';
import { MutexService } from '@jumentix/mutex-service';

const kv = compileKeyValueStorageClient(); // Redis em produção
await kv.connect();
const mutex = MutexService.compile(kv, { prefix: 'myapp-mutex' });

Use prefixo dedicado por serviço para chaves de lock não colidirem com cache.

6. Superfície completa — mapa da API

MembroRetornaNotas
MutexService.compile(kv, options?)Singleton MutexServiceExige client KV conectado
lock(resourceName, uuid)ServiceResponse com { locked, previouslyLocked }Não bloqueia esperando; retorna estado ocupado
isLocked(resourceName, uuid)ServiceResponse<boolean>Verifica marcador no KV
unlock(resourceName, uuid)ServiceResponseDelete idempotente da chave de lock
MutexService.reset()voidHelper de teste — limpa singleton

Este pacote não implementa fila nem renovação de lease — quem chama repete ou recua quando locked: false.

Experimente no playground de docs

Mutex com KV em memória

Proteja uma atualização de Category enquanto dois escritores de Task competem pelo mesmo recurso.

const keyValue = api.createKeyValueStorage();
const mutex = api.create(keyValue);

const firstWriter = await mutex.lock('category', 'work');
const secondWriter = await mutex.lock('category', 'work');
const lockedBeforeRelease = await mutex.isLocked('category', 'work');
await mutex.unlock('category', 'work');
const lockedAfterRelease = await mutex.isLocked('category', 'work');

return {
  firstWriter: firstWriter.result,
  secondWriter: secondWriter.result,
  lockedBeforeRelease: lockedBeforeRelease.result,
  lockedAfterRelease: lockedAfterRelease.result
};

O playground usa API simplificada acquire/release:

const mutex = api.create();
const lock = await mutex.acquire('docs-demo');
await mutex.release(lock);

Mapeie mentalmente para lock / unlock com nome de recurso e uuid no código real.

Erros comuns

SintomaCausaCorreçãoVerificar sucesso
locked: false sempreMesmo recurso já lockadoEspere ou use outro nome; chame unlock no fluxo anteriorSegunda tentativa após unlock funciona
Lock nunca liberadoFalta finally / return antecipadoEnvolva corpo em try/finally com unlockisLocked false após fluxo
Lock obsoleto após crashProcesso morreu antes de unlockEstratégia TTL fora deste pacote ou unlock manual com uuid conhecidoRecurso gravável de novo
MutexService depends on KeyValueStorageClientPassou client KV nuloCompile KV primeiroMutexService.compile(kv) funciona
Testes interferemMutex singleton + KV compartilhadoMutexService.reset() entre testesResultados de lock isolados por teste

Checklist júnior (“Eu consigo …”)

  • Ligar MutexService.compile com client KV in-memory.
  • Adquirir lock com uuid novo e liberar em finally.
  • Explicar diferença entre locked: false e resposta com error.
  • Manter seção crítica pequena e sem cadeias longas de await fetch.
  • Configurar prefix customizado para Redis de produção.
  • Descrever quando não usar locks in-memory (workers multi-processo).

Próximo passo

Volte à porta de armazenamento em key-value-storage e aprenda mensageria desacoplada em message-mediator.