@aichatwar/shared 1.0.102 → 1.0.104
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,6 +7,10 @@ export declare abstract class Listener<T extends BaseEvent> {
|
|
|
7
7
|
protected consumer: Consumer;
|
|
8
8
|
protected ackDeadline: number;
|
|
9
9
|
private currentPayload?;
|
|
10
|
+
private retryCount;
|
|
11
|
+
private readonly maxInitialRetries;
|
|
12
|
+
private readonly maxRetryDelay;
|
|
13
|
+
private isListening;
|
|
10
14
|
constructor(consumer: Consumer);
|
|
11
15
|
ack(): Promise<void>;
|
|
12
16
|
listen(): Promise<void>;
|
|
@@ -13,6 +13,10 @@ exports.Listener = void 0;
|
|
|
13
13
|
class Listener {
|
|
14
14
|
constructor(consumer) {
|
|
15
15
|
this.ackDeadline = 5 * 1000; // 5 seconds
|
|
16
|
+
this.retryCount = 0;
|
|
17
|
+
this.maxInitialRetries = 5; // Show detailed retry logs for first 5 attempts
|
|
18
|
+
this.maxRetryDelay = 60000; // Cap delay at 60 seconds
|
|
19
|
+
this.isListening = false; // Track if listener is active
|
|
16
20
|
this.consumer = consumer;
|
|
17
21
|
}
|
|
18
22
|
// Manual acknowledgment method
|
|
@@ -31,36 +35,88 @@ class Listener {
|
|
|
31
35
|
}
|
|
32
36
|
listen() {
|
|
33
37
|
return __awaiter(this, void 0, void 0, function* () {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
var _a;
|
|
39
|
+
// Prevent multiple simultaneous listen attempts
|
|
40
|
+
if (this.isListening) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
yield this.consumer.connect();
|
|
45
|
+
// Handle consumer errors (including partition errors)
|
|
46
|
+
this.consumer.on('consumer.crash', (event) => {
|
|
47
|
+
var _a;
|
|
48
|
+
const error = event.payload.error;
|
|
49
|
+
if ((_a = error === null || error === void 0 ? void 0 : error.message) === null || _a === void 0 ? void 0 : _a.includes('does not host this topic-partition')) {
|
|
50
|
+
console.warn(`[${this.topic}] Consumer partition error (non-fatal):`, error.message);
|
|
51
|
+
// Don't crash - this is often a transient error
|
|
43
52
|
return;
|
|
44
|
-
console.log(`Message received -> topic: ${this.topic}, groupId: ${this.groupId}`);
|
|
45
|
-
// Store current payload for manual ack
|
|
46
|
-
this.currentPayload = payload;
|
|
47
|
-
try {
|
|
48
|
-
const data = JSON.parse(payload.message.value.toString());
|
|
49
|
-
yield this.onMessage(data, payload);
|
|
50
|
-
// Note: Child listeners must call this.ack() manually after successful processing
|
|
51
|
-
// If they don't call ack(), the message will be redelivered
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
console.error(`[${this.topic}] Consumer crashed:`, error);
|
|
55
|
+
});
|
|
56
|
+
yield this.consumer.subscribe({
|
|
57
|
+
topic: this.topic,
|
|
58
|
+
fromBeginning: false,
|
|
59
|
+
});
|
|
60
|
+
// Reset retry count on successful connection
|
|
61
|
+
this.retryCount = 0;
|
|
62
|
+
console.log(`[${this.topic}] Listening to topic with groupId: ${this.groupId}`);
|
|
63
|
+
yield this.consumer.run({
|
|
64
|
+
eachMessage: (payload) => __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
if (!payload.message.value)
|
|
66
|
+
return;
|
|
67
|
+
console.log(`Message received -> topic: ${this.topic}, groupId: ${this.groupId}`);
|
|
68
|
+
// Store current payload for manual ack
|
|
69
|
+
this.currentPayload = payload;
|
|
70
|
+
try {
|
|
71
|
+
const data = JSON.parse(payload.message.value.toString());
|
|
72
|
+
yield this.onMessage(data, payload);
|
|
73
|
+
// Note: Child listeners must call this.ack() manually after successful processing
|
|
74
|
+
// If they don't call ack(), the message will be redelivered
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error(`Error processing message for topic: ${this.topic}`, error);
|
|
78
|
+
// In case of error, we don't commit the offset, so the message will be redelivered
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
// Clear the current payload
|
|
83
|
+
this.currentPayload = undefined;
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
});
|
|
87
|
+
// Mark as successfully listening only after subscription and run are successful
|
|
88
|
+
this.isListening = true;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
// Reset listening flag on error so we can retry
|
|
92
|
+
this.isListening = false;
|
|
93
|
+
// Handle connection/subscription errors gracefully
|
|
94
|
+
// The "This server does not host this topic-partition" error is often harmless
|
|
95
|
+
// and occurs when KafkaJS tries to list offsets for a topic that doesn't exist yet or has no messages
|
|
96
|
+
if ((_a = error === null || error === void 0 ? void 0 : error.message) === null || _a === void 0 ? void 0 : _a.includes('does not host this topic-partition')) {
|
|
97
|
+
this.retryCount++;
|
|
98
|
+
// Calculate exponential backoff delay (cap at maxRetryDelay)
|
|
99
|
+
const delay = Math.min(5000 * Math.pow(2, Math.min(this.retryCount - 1, 6)), this.maxRetryDelay);
|
|
100
|
+
// Show detailed logs for first few attempts, then less frequently
|
|
101
|
+
if (this.retryCount <= this.maxInitialRetries) {
|
|
102
|
+
console.warn(`[${this.topic}] Topic partition error (attempt ${this.retryCount}/${this.maxInitialRetries}): ${error.message}. Retrying in ${delay}ms...`);
|
|
57
103
|
}
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
this.
|
|
104
|
+
else if (this.retryCount % 10 === 0) {
|
|
105
|
+
// Log every 10th retry after initial attempts to avoid log spam
|
|
106
|
+
console.warn(`[${this.topic}] Still retrying subscription (attempt ${this.retryCount}). Topic may not exist yet or has no data. Retrying in ${delay}ms...`);
|
|
61
107
|
}
|
|
62
|
-
|
|
63
|
-
|
|
108
|
+
// Retry indefinitely with exponential backoff
|
|
109
|
+
// This allows the listener to connect once the topic becomes available
|
|
110
|
+
setTimeout(() => {
|
|
111
|
+
if (!this.isListening) {
|
|
112
|
+
this.listen();
|
|
113
|
+
}
|
|
114
|
+
}, delay);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
console.error(`[${this.topic}] Error setting up listener:`, error);
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
64
120
|
});
|
|
65
121
|
}
|
|
66
122
|
}
|