Skip to content

Commit a12e389

Browse files
committed
fix: improve monochrome instance selection and latency
- Implement initial latency check during setup to score and prioritize fast instances. - Add 5-second timeout to API requests to prevent worker hanging on dead servers. - Sync health scores between API and streaming pools. - Increase selection randomization to prevent instance overloading.
1 parent b003855 commit a12e389

2 files changed

Lines changed: 105 additions & 18 deletions

File tree

dist/src/sources/monochrome.js

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,59 @@ class MonochromeSource {
7272
}
7373
/**
7474
* Performs provider-specific resource initialization.
75+
* Runs an initial latency check to score instances.
7576
* @returns A promise resolving to true if initialized.
7677
*/
7778
async setup() {
7879
const apiCount = this.apiInstances.length;
7980
const streamCount = this.streamingInstances.length;
80-
if (apiCount > 0) {
81-
logger('info', 'Monochrome', `Source is ready with ${apiCount} API and ${streamCount} streaming instances.`);
82-
return true;
81+
if (apiCount === 0) {
82+
logger('warn', 'Monochrome', 'Source failed to initialize: No instances available.');
83+
return false;
84+
}
85+
logger('info', 'Monochrome', `Initializing latency check for ${apiCount} instances...`);
86+
const checkInstance = async (instance) => {
87+
const start = Date.now();
88+
try {
89+
const { statusCode } = await makeRequest(instance.url + '/', {
90+
method: 'GET',
91+
timeout: 3000
92+
});
93+
const latency = Date.now() - start;
94+
if (statusCode === 200 || statusCode === 404 || statusCode === 302) {
95+
// Success (or at least reachable)
96+
if (latency < 500)
97+
instance.score = 100;
98+
else if (latency < 1500)
99+
instance.score = 80;
100+
else if (latency < 3000)
101+
instance.score = 50;
102+
else
103+
instance.score = 20;
104+
logger('debug', 'Monochrome', `Instance ${instance.url} - Latency: ${latency}ms, Initial Score: ${instance.score}`);
105+
}
106+
else {
107+
instance.score = 0;
108+
instance.lastFailure = Date.now();
109+
logger('debug', 'Monochrome', `Instance ${instance.url} - Error: Status ${statusCode}, Score: 0`);
110+
}
111+
}
112+
catch (e) {
113+
instance.score = 0;
114+
instance.lastFailure = Date.now();
115+
logger('debug', 'Monochrome', `Instance ${instance.url} - Connection Failed, Score: 0`);
116+
}
117+
};
118+
await Promise.allSettled(this.apiInstances.map(checkInstance));
119+
// Sync streaming scores if they use the same URLs
120+
for (const s of this.streamingInstances) {
121+
const api = this.apiInstances.find(a => a.url === s.url);
122+
if (api)
123+
s.score = api.score;
83124
}
84-
logger('warn', 'Monochrome', 'Source failed to initialize: No instances available.');
85-
return false;
125+
const reachable = this.apiInstances.filter(i => i.score > 0).length;
126+
logger('info', 'Monochrome', `Source is ready with ${apiCount} API (${reachable} reachable) and ${streamCount} streaming instances.`);
127+
return true;
86128
}
87129
/**
88130
* Selects the healthiest instance from the pool using a scored random strategy.
@@ -94,14 +136,15 @@ class MonochromeSource {
94136
getBestInstance(type = 'api', minVersion) {
95137
const pool = type === 'streaming' ? this.streamingInstances : this.apiInstances;
96138
const now = Date.now();
97-
let candidates = pool.filter((i) => (i.score > 0 || now - i.lastFailure > 30_000) &&
139+
let candidates = pool.filter((i) => (i.score > 0 || now - i.lastFailure > 60_000) &&
98140
(!minVersion || (i.version && i.version >= minVersion)));
99141
if (candidates.length === 0 && minVersion) {
100-
candidates = pool.filter((i) => i.score > 0 || now - i.lastFailure > 30_000);
142+
candidates = pool.filter((i) => i.score > 0 || now - i.lastFailure > 60_000);
101143
}
102144
const activePool = candidates.length > 0 ? candidates : pool;
103145
const sorted = activePool.sort((a, b) => b.score - a.score || a.activeRequests - b.activeRequests);
104-
const instance = sorted[Math.floor(Math.random() * Math.min(sorted.length, 3))] || pool[0];
146+
// Increase randomization factor to 5 to avoid overloading the very best one
147+
const instance = sorted[Math.floor(Math.random() * Math.min(sorted.length, 5))] || pool[0];
105148
if (!instance) {
106149
throw new Error('No instances available in pool');
107150
}
@@ -125,6 +168,7 @@ class MonochromeSource {
125168
instance.activeRequests++;
126169
try {
127170
const { body, error, statusCode } = await makeRequest(url, {
171+
timeout: 5000,
128172
headers: {
129173
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36',
130174
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',

src/sources/monochrome.ts

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,27 +102,68 @@ class MonochromeSource implements SourceInstance {
102102

103103
/**
104104
* Performs provider-specific resource initialization.
105+
* Runs an initial latency check to score instances.
105106
* @returns A promise resolving to true if initialized.
106107
*/
107108
public async setup(): Promise<boolean> {
108109
const apiCount = this.apiInstances.length
109110
const streamCount = this.streamingInstances.length
110111

111-
if (apiCount > 0) {
112+
if (apiCount === 0) {
112113
logger(
113-
'info',
114+
'warn',
114115
'Monochrome',
115-
`Source is ready with ${apiCount} API and ${streamCount} streaming instances.`
116+
'Source failed to initialize: No instances available.'
116117
)
117-
return true
118+
return false
118119
}
119120

121+
logger('info', 'Monochrome', `Initializing latency check for ${apiCount} instances...`)
122+
123+
const checkInstance = async (instance: InstanceHealth) => {
124+
const start = Date.now()
125+
try {
126+
const { statusCode } = await makeRequest(instance.url + '/', {
127+
method: 'GET',
128+
timeout: 3000
129+
})
130+
const latency = Date.now() - start
131+
132+
if (statusCode === 200 || statusCode === 404 || statusCode === 302) {
133+
// Success (or at least reachable)
134+
if (latency < 500) instance.score = 100
135+
else if (latency < 1500) instance.score = 80
136+
else if (latency < 3000) instance.score = 50
137+
else instance.score = 20
138+
139+
logger('debug', 'Monochrome', `Instance ${instance.url} - Latency: ${latency}ms, Initial Score: ${instance.score}`)
140+
} else {
141+
instance.score = 0
142+
instance.lastFailure = Date.now()
143+
logger('debug', 'Monochrome', `Instance ${instance.url} - Error: Status ${statusCode}, Score: 0`)
144+
}
145+
} catch (e) {
146+
instance.score = 0
147+
instance.lastFailure = Date.now()
148+
logger('debug', 'Monochrome', `Instance ${instance.url} - Connection Failed, Score: 0`)
149+
}
150+
}
151+
152+
await Promise.allSettled(this.apiInstances.map(checkInstance))
153+
154+
// Sync streaming scores if they use the same URLs
155+
for (const s of this.streamingInstances) {
156+
const api = this.apiInstances.find(a => a.url === s.url)
157+
if (api) s.score = api.score
158+
}
159+
160+
const reachable = this.apiInstances.filter(i => i.score > 0).length
120161
logger(
121-
'warn',
162+
'info',
122163
'Monochrome',
123-
'Source failed to initialize: No instances available.'
164+
`Source is ready with ${apiCount} API (${reachable} reachable) and ${streamCount} streaming instances.`
124165
)
125-
return false
166+
return true
126167
}
127168

128169
/**
@@ -142,13 +183,13 @@ class MonochromeSource implements SourceInstance {
142183

143184
let candidates = pool.filter(
144185
(i) =>
145-
(i.score > 0 || now - i.lastFailure > 30_000) &&
186+
(i.score > 0 || now - i.lastFailure > 60_000) &&
146187
(!minVersion || (i.version && i.version >= minVersion))
147188
)
148189

149190
if (candidates.length === 0 && minVersion) {
150191
candidates = pool.filter(
151-
(i) => i.score > 0 || now - i.lastFailure > 30_000
192+
(i) => i.score > 0 || now - i.lastFailure > 60_000
152193
)
153194
}
154195

@@ -158,8 +199,9 @@ class MonochromeSource implements SourceInstance {
158199
(a, b) => b.score - a.score || a.activeRequests - b.activeRequests
159200
)
160201

202+
// Increase randomization factor to 5 to avoid overloading the very best one
161203
const instance =
162-
sorted[Math.floor(Math.random() * Math.min(sorted.length, 3))] || pool[0]
204+
sorted[Math.floor(Math.random() * Math.min(sorted.length, 5))] || pool[0]
163205
if (!instance) {
164206
throw new Error('No instances available in pool')
165207
}
@@ -191,6 +233,7 @@ class MonochromeSource implements SourceInstance {
191233
instance.activeRequests++
192234
try {
193235
const { body, error, statusCode } = await makeRequest(url, {
236+
timeout: 5000,
194237
headers: {
195238
'User-Agent':
196239
'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36',

0 commit comments

Comments
 (0)