Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
254
vendor/ruvector/examples/edge/pkg/worker-pool.js
vendored
Normal file
254
vendor/ruvector/examples/edge/pkg/worker-pool.js
vendored
Normal file
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* Web Worker Pool Manager
|
||||
*
|
||||
* Manages a pool of workers for parallel vector operations.
|
||||
* Supports:
|
||||
* - Round-robin task distribution
|
||||
* - Load balancing
|
||||
* - Automatic worker initialization
|
||||
* - Promise-based API
|
||||
*/
|
||||
|
||||
export class WorkerPool {
|
||||
constructor(workerUrl, wasmUrl, options = {}) {
|
||||
this.workerUrl = workerUrl;
|
||||
this.wasmUrl = wasmUrl;
|
||||
this.poolSize = options.poolSize || navigator.hardwareConcurrency || 4;
|
||||
this.workers = [];
|
||||
this.nextWorker = 0;
|
||||
this.pendingRequests = new Map();
|
||||
this.requestId = 0;
|
||||
this.initialized = false;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the worker pool
|
||||
*/
|
||||
async init() {
|
||||
if (this.initialized) return;
|
||||
|
||||
console.log(`Initializing worker pool with ${this.poolSize} workers...`);
|
||||
|
||||
const initPromises = [];
|
||||
|
||||
for (let i = 0; i < this.poolSize; i++) {
|
||||
const worker = new Worker(this.workerUrl, { type: 'module' });
|
||||
|
||||
worker.onmessage = (e) => this.handleMessage(i, e);
|
||||
worker.onerror = (error) => this.handleError(i, error);
|
||||
|
||||
this.workers.push({
|
||||
worker,
|
||||
busy: false,
|
||||
id: i
|
||||
});
|
||||
|
||||
// Initialize worker with WASM
|
||||
const initPromise = this.sendToWorker(i, 'init', {
|
||||
wasmUrl: this.wasmUrl,
|
||||
dimensions: this.options.dimensions,
|
||||
metric: this.options.metric,
|
||||
useHnsw: this.options.useHnsw
|
||||
});
|
||||
|
||||
initPromises.push(initPromise);
|
||||
}
|
||||
|
||||
await Promise.all(initPromises);
|
||||
this.initialized = true;
|
||||
|
||||
console.log(`Worker pool initialized successfully`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle message from worker
|
||||
*/
|
||||
handleMessage(workerId, event) {
|
||||
const { type, requestId, data, error } = event.data;
|
||||
|
||||
if (type === 'error') {
|
||||
const request = this.pendingRequests.get(requestId);
|
||||
if (request) {
|
||||
request.reject(new Error(error.message));
|
||||
this.pendingRequests.delete(requestId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const request = this.pendingRequests.get(requestId);
|
||||
if (request) {
|
||||
this.workers[workerId].busy = false;
|
||||
request.resolve(data);
|
||||
this.pendingRequests.delete(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle worker error
|
||||
*/
|
||||
handleError(workerId, error) {
|
||||
console.error(`Worker ${workerId} error:`, error);
|
||||
|
||||
// Reject all pending requests for this worker
|
||||
for (const [requestId, request] of this.pendingRequests) {
|
||||
if (request.workerId === workerId) {
|
||||
request.reject(error);
|
||||
this.pendingRequests.delete(requestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next available worker (round-robin)
|
||||
*/
|
||||
getNextWorker() {
|
||||
// Try to find an idle worker
|
||||
for (let i = 0; i < this.workers.length; i++) {
|
||||
const idx = (this.nextWorker + i) % this.workers.length;
|
||||
if (!this.workers[idx].busy) {
|
||||
this.nextWorker = (idx + 1) % this.workers.length;
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
||||
// All busy, use round-robin
|
||||
const idx = this.nextWorker;
|
||||
this.nextWorker = (this.nextWorker + 1) % this.workers.length;
|
||||
return idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to specific worker
|
||||
*/
|
||||
sendToWorker(workerId, type, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestId = this.requestId++;
|
||||
|
||||
this.pendingRequests.set(requestId, {
|
||||
resolve,
|
||||
reject,
|
||||
workerId,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
this.workers[workerId].busy = true;
|
||||
this.workers[workerId].worker.postMessage({
|
||||
type,
|
||||
data: { ...data, requestId }
|
||||
});
|
||||
|
||||
// Timeout after 30 seconds
|
||||
setTimeout(() => {
|
||||
if (this.pendingRequests.has(requestId)) {
|
||||
this.pendingRequests.delete(requestId);
|
||||
reject(new Error('Request timeout'));
|
||||
}
|
||||
}, 30000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute operation on next available worker
|
||||
*/
|
||||
async execute(type, data) {
|
||||
if (!this.initialized) {
|
||||
await this.init();
|
||||
}
|
||||
|
||||
const workerId = this.getNextWorker();
|
||||
return this.sendToWorker(workerId, type, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert vector
|
||||
*/
|
||||
async insert(vector, id = null, metadata = null) {
|
||||
return this.execute('insert', { vector, id, metadata });
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert batch of vectors
|
||||
*/
|
||||
async insertBatch(entries) {
|
||||
// Distribute batch across workers
|
||||
const chunkSize = Math.ceil(entries.length / this.poolSize);
|
||||
const chunks = [];
|
||||
|
||||
for (let i = 0; i < entries.length; i += chunkSize) {
|
||||
chunks.push(entries.slice(i, i + chunkSize));
|
||||
}
|
||||
|
||||
const promises = chunks.map((chunk, i) =>
|
||||
this.sendToWorker(i % this.poolSize, 'insertBatch', { entries: chunk })
|
||||
);
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
return results.flat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for similar vectors
|
||||
*/
|
||||
async search(query, k = 10, filter = null) {
|
||||
return this.execute('search', { query, k, filter });
|
||||
}
|
||||
|
||||
/**
|
||||
* Parallel search across multiple queries
|
||||
*/
|
||||
async searchBatch(queries, k = 10, filter = null) {
|
||||
const promises = queries.map((query, i) =>
|
||||
this.sendToWorker(i % this.poolSize, 'search', { query, k, filter })
|
||||
);
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete vector
|
||||
*/
|
||||
async delete(id) {
|
||||
return this.execute('delete', { id });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get vector by ID
|
||||
*/
|
||||
async get(id) {
|
||||
return this.execute('get', { id });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database length (from first worker)
|
||||
*/
|
||||
async len() {
|
||||
return this.sendToWorker(0, 'len', {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate all workers
|
||||
*/
|
||||
terminate() {
|
||||
for (const { worker } of this.workers) {
|
||||
worker.terminate();
|
||||
}
|
||||
this.workers = [];
|
||||
this.initialized = false;
|
||||
console.log('Worker pool terminated');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pool statistics
|
||||
*/
|
||||
getStats() {
|
||||
return {
|
||||
poolSize: this.poolSize,
|
||||
busyWorkers: this.workers.filter(w => w.busy).length,
|
||||
idleWorkers: this.workers.filter(w => !w.busy).length,
|
||||
pendingRequests: this.pendingRequests.size
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default WorkerPool;
|
||||
Reference in New Issue
Block a user