UNPKG

pg-transactional-outbox

Version:

A PostgreSQL based transactional outbox and inbox pattern implementation to support exactly once message processing (with at least once message delivery).

108 lines 6.55 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createErrorHandler = void 0; const database_1 = require("../common/database"); const error_1 = require("../common/error"); const utils_1 = require("../common/utils"); const increase_message_finished_attempts_1 = require("../message/increase-message-finished-attempts"); const mark_message_abandoned_1 = require("../message/mark-message-abandoned"); const message_handler_selector_1 = require("./message-handler-selector"); /** * Create a function to execute the error handling orchestration logic for one * message. It executes the retry attempts logic and executes the message * handlers `handleError` function if it exists. * That executor function will not throw an error. */ const createErrorHandler = (messageHandlers, strategies, config, logger) => { const handlerSelector = (0, message_handler_selector_1.messageHandlerSelector)(messageHandlers); /** * An error handler that will increase the "finished_attempts" counter. * The message handler `handleError` is called to allow your code to handle or * resolve the error. * @param message: the stored message that failed to be processed * @param error: the error that was thrown while processing the message * @returns A boolean value if the message should be retried again in the context of the replication listener */ return (message, error) => __awaiter(void 0, void 0, void 0, function* () { const handler = handlerSelector(message); const transactionLevel = strategies.messageProcessingTransactionLevelStrategy(message); try { return yield (0, utils_1.executeTransaction)(yield strategies.messageProcessingDbClientStrategy.getClient(message), (client) => __awaiter(void 0, void 0, void 0, function* () { message.finishedAttempts++; const shouldRetry = strategies.messageRetryStrategy(message, error, 'message-handler'); if (handler === null || handler === void 0 ? void 0 : handler.handleError) { yield handler.handleError(error, message, client, shouldRetry); } if (shouldRetry) { yield (0, increase_message_finished_attempts_1.increaseMessageFinishedAttempts)(message, client, config); Object.assign(error, { messageObject: message }); logger.warn(error, `An error ocurred while processing the ${config.outboxOrInbox} message with id ${message.id}. Retrying it again.`); } else { yield (0, mark_message_abandoned_1.markMessageAbandoned)(message, client, config); logger.error(new error_1.MessageError(error.message, 'GIVING_UP_MESSAGE_HANDLING', message, error), `Giving up processing the ${config.outboxOrInbox} message with id ${message.id}.`); } return shouldRetry; }), transactionLevel); } catch (err) { const msg = handler ? `The error handling of the ${config.outboxOrInbox} message failed. Please make sure that your error handling code does not throw an error!` : `The error handling of the ${config.outboxOrInbox} message failed.`; const error = new error_1.MessageError(msg, 'MESSAGE_ERROR_HANDLING_FAILED', message, err); const shouldRetry = strategies.messageRetryStrategy(message, error, 'message-handler'); updateMessageText(error, shouldRetry); logger.error(error, error.message); // In case the error handling logic failed do a best effort to increase the "finished_attempts" counter or abandon the message try { yield bestEffortMessageUpdate(strategies, message, shouldRetry, config, transactionLevel); return strategies.messageRetryStrategy(message, error, 'error-handler'); } catch (bestEffortError) { const e = new error_1.MessageError(`The 'best-effort' logic to increase the ${config.outboxOrInbox} message finished attempts failed as well.`, 'MESSAGE_ERROR_HANDLING_FAILED', message, bestEffortError); const shouldRetry = strategies.messageRetryStrategy(message, e, 'error-handler-error'); updateMessageText(e, shouldRetry); logger.error(e, e.message); return shouldRetry; } } }); }; exports.createErrorHandler = createErrorHandler; const updateMessageText = (e, shouldRetry) => (e.message = `${e.message} Attempting to ${shouldRetry ? 'retry' : 'abandon'} the message.`); const bestEffortMessageUpdate = (strategies, message, shouldRetry, config, transactionLevel) => __awaiter(void 0, void 0, void 0, function* () { let i = 0; do { try { yield (0, utils_1.executeTransaction)(yield strategies.messageProcessingDbClientStrategy.getClient(message), (client) => __awaiter(void 0, void 0, void 0, function* () { if (shouldRetry) { yield (0, increase_message_finished_attempts_1.increaseMessageFinishedAttempts)(message, client, config); } else { yield (0, mark_message_abandoned_1.markMessageAbandoned)(message, client, config); } }), transactionLevel); return; } catch (error) { i++; // retry for serialization failure = 40001 and deadlock detected = 40P01 PG errors if (i < 3 && (0, database_1.isPgSerializationError)(error)) { yield (0, utils_1.sleep)(i * 100); } else { throw error; } } } while (i < 3); }); //# sourceMappingURL=create-error-handler.js.map