swadhinbiswas feat: OpenCodeHub v1.1.0 - Enterprise Features & Storage Adapters · 2/15/2026
719a1cb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/**
* Distributed Lock Utility
* Uses Redis for distributed locking across multiple instances
* Implements a simple lock with TTL and retry mechanism
*/
import { logger } from "./logger";
// Try to import Redis - may not be configured
let redis: any = null;
let redisAvailable = false;
try {
const redisModule = await import("./redis");
redis = redisModule.redis;
// Test connection
await redis.ping();
redisAvailable = true;
} catch (e) {
if (import.meta.env.PROD) {
throw new Error("CRITICAL: Redis is required in production for distributed locking. Please configure REDIS_URL.");
}
logger.warn("Redis not available - distributed locks disabled. Using in-memory fallback. NOT SAFE for multi-instance deployments.");
}
export interface LockOptions {
/** Lock timeout in seconds (default: 30) */
ttlSeconds?: number;
/** Retry attempts if lock is held (default: 10) */
retryCount?: number;
/** Delay between retries in ms (default: 100) */
retryDelayMs?: number;
}
export interface Lock {
key: string;
token: string;
release: () => Promise<boolean>;
}
/**
* In-memory lock for single-instance deployments (fallback)
*/
class InMemoryLockManager {
private locks: Map<string, { token: string; expiresAt: number }> = new Map();
private cleanupInterval: NodeJS.Timeout;
constructor() {
// Cleanup expired locks every 5 seconds
this.cleanupInterval = setInterval(() => this.cleanup(), 5000);
}
private cleanup() {
const now = Date.now();
for (const [key, lock] of this.locks.entries()) {
if (now > lock.expiresAt) {
this.locks.delete(key);
}
}
}
async acquire(key: string, options: LockOptions = {}): Promise<Lock | null> {
const {
ttlSeconds = 30,
retryCount = 10,
retryDelayMs = 100,
} = options;
const token = crypto.randomUUID();
const expiresAt = Date.now() + ttlSeconds * 1000;
for (let attempt = 0; attempt <= retryCount; attempt++) {
const existing = this.locks.get(key);
if (!existing || Date.now() > existing.expiresAt) {
this.locks.set(key, { token, expiresAt });
return {
key,
token,
release: async () => {
const current = this.locks.get(key);
if (current?.token === token) {
this.locks.delete(key);
return true;
}
return false;
}
};
}
if (attempt < retryCount) {
await new Promise(resolve => setTimeout(resolve, retryDelayMs));
}
}
return null; // Failed to acquire lock
}
destroy() {
clearInterval(this.cleanupInterval);
}
}
/**
* Redis-based distributed lock manager using SET NX with expiry
*/
class RedisLockManager {
/**
* Acquire a distributed lock
* Uses SET NX (only set if not exists) with expiry for safety
*/
async acquire(key: string, options: LockOptions = {}): Promise<Lock | null> {
const {
ttlSeconds = 30,
retryCount = 10,
retryDelayMs = 100,
} = options;
const lockKey = `lock:${key}`;
const token = crypto.randomUUID();
for (let attempt = 0; attempt <= retryCount; attempt++) {
try {
// SET key value NX EX seconds
// NX = only set if not exists
// EX = expire in seconds
const result = await redis.set(lockKey, token, {
nx: true,
ex: ttlSeconds,
});
if (result === "OK") {
logger.debug({ key, token, ttl: ttlSeconds }, "Lock acquired");
return {
key,
token,
release: async () => {
return this.release(lockKey, token);
}
};
}
// Lock is held by someone else
if (attempt < retryCount) {
await new Promise(resolve => setTimeout(resolve, retryDelayMs));
}
} catch (error) {
logger.error({ error, key }, "Error acquiring lock");
// On Redis error, we fail open (allow operation)
// This is a trade-off between availability and consistency
return {
key,
token: "fallback",
release: async () => true,
};
}
}
logger.warn({ key, attempts: retryCount + 1 }, "Failed to acquire lock after retries");
return null;
}
/**
* Release a lock only if we still own it
* Uses Lua script for atomic check-and-delete
*/
private async release(lockKey: string, token: string): Promise<boolean> {
try {
// Lua script for atomic check-and-delete
// Only deletes if the value matches our token
const script = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`;
const result = await redis.eval(script, [lockKey], [token]);
const released = result === 1;
logger.debug({ key: lockKey, released }, "Lock release attempt");
return released;
} catch (error) {
logger.error({ error, key: lockKey }, "Error releasing lock");
return false;
}
}
destroy() {
// Redis handles connections automatically with Upstash
}
}
// Export the appropriate lock manager
const lockManager = redisAvailable ? new RedisLockManager() : new InMemoryLockManager();
export const isDistributedLocking = redisAvailable;
/**
* Acquire a distributed lock
* @param key - Unique key for the lock
* @param options - Lock options (TTL, retries)
* @returns Lock object with release function, or null if failed
*/
export async function acquireLock(key: string, options?: LockOptions): Promise<Lock | null> {
return lockManager.acquire(key, options);
}
/**
* Execute a function with a distributed lock
* Automatically acquires and releases the lock
* @param key - Unique key for the lock
* @param fn - Function to execute while holding the lock
* @param options - Lock options
* @returns Result of the function, or throws if lock cannot be acquired
*/
export async function withLock<T>(
key: string,
fn: () => Promise<T>,
options?: LockOptions
): Promise<T> {
const lock = await acquireLock(key, options);
if (!lock) {
throw new Error(`Failed to acquire lock for key: ${key}`);
}
try {
return await fn();
} finally {
await lock.release();
}
}
export default lockManager;