UNPKG

get-lat-long-queue

Version:

A node js redis package that creates a queue of requests and processes with FIFO principles, spacing requests, and retrieving lattitude and longitude data for a specified address from Nominatim API.

119 lines (101 loc) 5.14 kB
const geosearch = require("./Geo/geoSearch"); const { acquireLock, calculateAppointmentTime } = require("./Middleware/lock"); const { delay, addToQueue } = require("./Middleware/queue"); const getLatLong = async ( redisClient, latLongQueueKey, latLongLockKey, latLongLocalLockKey, latLongProcessingKey, expirationTime, { id, locationToSearch }, displayLogs ) => { // Retrieve and store timestamp to identify specific requests irrespective of the user. const requestTimestamp = Date.now(); displayLogs ? console.log(`user id: ${id}. GetLatLong called at ${new Date(Date.now()).toLocaleString()}`) : ''; // Add to queue with details and retrieve position, previous execution time, and process status. Check Middleware/queue.js. const queueDetails = await addToQueue( redisClient, latLongQueueKey, latLongProcessingKey, latLongLockKey, latLongLocalLockKey, { id, locationToSearch, requestTimestamp }, displayLogs ); //Destructure the object received to queueDetails const queuePosition = queueDetails.position; const lastExecutionTimeLock = queueDetails.tempLock; const lastExecutionTimeLocal = queueDetails.lastTimeKeyValue; let processStatus = queueDetails.processStatus; // In order to avoid race conditions, we need to consider four scenarios: // - no one is in the queue i.e. lock has expired and queuePosition is first. // - lock hasn't expired but first one in the queue. Wait for lock to expire and execute. // - not first in the queue but process hasn't started yet. Wait for object in front of the queue to start the process. // - process is already in order. Just calculate your appointment time. // Except when first, all users will wait for lock to expire before firing. This is to avoid unnecessary processes. // Time accurate to milliseconds of lock creation is saved in the lock on execution. See middleware/lock.js. // Calculate appointment time based on queue position and previous execution time and wait in the waiting room. let appointmentTime; let waitingRoom; if (isNaN(lastExecutionTimeLock) && queuePosition === 0) { waitingRoom = 0; displayLogs ? console.log(`user id: ${id}, no queue, lock expired, ${new Date(Date.now()).toLocaleString()}`) : ''; } else if (queuePosition === 0) { appointmentTime = Math.max(Date.now(), lastExecutionTimeLocal + expirationTime) waitingRoom = appointmentTime - Date.now() displayLogs ? console.log(`user id: ${id}, no queue, lock active, delay by ${waitingRoom / 1000} seconds, ${new Date(Date.now()).toLocaleString()}`) : ''; } else if (processStatus === false) { // To avoid race conditions, need to wait for first in the queue to start process. Details in lock.js. let processCounter = 0; while (processStatus === false) { let processStatusString = await redisClient.GET(latLongProcessingKey); processStatus = processStatusString === 'true' ? true : false processCounter++ await delay(100); // Check every 100ms } appointmentTime = await calculateAppointmentTime(queuePosition, lastExecutionTimeLocal, id, expirationTime, displayLogs) waitingRoom = appointmentTime - Date.now() displayLogs ? console.log(`user id: ${id}, queue, process inactive, tried ${processCounter} times, delay by ${waitingRoom / 1000} seconds, ${new Date(Date.now()).toLocaleString()}`) : ''; } else { appointmentTime = await calculateAppointmentTime(queuePosition, lastExecutionTimeLocal, id, expirationTime, displayLogs) waitingRoom = appointmentTime - Date.now() displayLogs ? console.log(`user id: ${id}, queue and process active, delay by ${waitingRoom / 1000} seconds, ${new Date(Date.now()).toLocaleString()}`) : ''; } // Wait for appointment time await delay(waitingRoom) displayLogs ? console.log(`user id: ${id}, after delay, ${new Date(Date.now()).toLocaleString()}`) : ''; // Try to acquire lock const lockStatus = await acquireLock( redisClient, latLongQueueKey, latLongLockKey, latLongLocalLockKey, latLongProcessingKey, expirationTime, id, displayLogs, ) // Since the lock is active for a supplied expiration time, we can guarantee spacing between requests. try { if (lockStatus === true) { const result = await makeApiCall(id, locationToSearch) return result; } else { console.log(`user id: ${id}, failed to acquire lock`); } } catch (err) { console.log(`user id: ${id} error while making external api call`) } }; const makeApiCall = async (id, locationToSearch) => { displayLogs ? console.log(`user id: ${id} made external api call at ${new Date(Date.now()).toLocaleString()}`) : ''; const result = await geosearch(locationToSearch); const { lat, lon } = result[0]; return { lat, lon }; } module.exports = getLatLong;