cache clearing through cluster messages
parent
60f0a13959
commit
1401786254
21
bin/www.js
21
bin/www.js
|
|
@ -7,20 +7,37 @@ import * as configFunctions from '../helpers/functions.config.js';
|
|||
import exitHook from 'exit-hook';
|
||||
import ntfyPublish from '@cityssm/ntfy-publish';
|
||||
import Debug from 'debug';
|
||||
const debug = Debug('lot-occupancy-system:www');
|
||||
const debug = Debug(`lot-occupancy-system:www:${process.pid}`);
|
||||
const directoryName = dirname(fileURLToPath(import.meta.url));
|
||||
const processCount = Math.min(configFunctions.getProperty('application.maximumProcesses'), os.cpus().length);
|
||||
process.title =
|
||||
configFunctions.getProperty('application.applicationName') + ' (Primary)';
|
||||
debug(`Primary pid: ${process.pid}`);
|
||||
debug(`Primary title: ${process.title}`);
|
||||
debug(`Launching ${processCount} processes`);
|
||||
const clusterSettings = {
|
||||
exec: directoryName + '/wwwProcess.js'
|
||||
};
|
||||
cluster.setupPrimary(clusterSettings);
|
||||
const activeWorkers = new Map();
|
||||
for (let index = 0; index < processCount; index += 1) {
|
||||
cluster.fork();
|
||||
const worker = cluster.fork();
|
||||
activeWorkers.set(worker.process.pid, worker);
|
||||
}
|
||||
cluster.on('message', (worker, message) => {
|
||||
if (message?.messageType === 'clearCache') {
|
||||
for (const [pid, worker] of activeWorkers.entries()) {
|
||||
if (worker === undefined || pid === message.pid) {
|
||||
continue;
|
||||
}
|
||||
debug('Relaying message to workers');
|
||||
worker.send(message);
|
||||
}
|
||||
}
|
||||
});
|
||||
cluster.on('exit', (worker, code, signal) => {
|
||||
debug(`Worker ${worker.process.pid.toString()} has been killed`);
|
||||
activeWorkers.delete(worker.process.pid);
|
||||
debug('Starting another worker');
|
||||
cluster.fork();
|
||||
});
|
||||
|
|
|
|||
28
bin/www.ts
28
bin/www.ts
|
|
@ -12,8 +12,10 @@ import exitHook from 'exit-hook'
|
|||
import ntfyPublish from '@cityssm/ntfy-publish'
|
||||
import type * as ntfyTypes from '@cityssm/ntfy-publish/types'
|
||||
|
||||
import type { WorkerMessage } from '../types/applicationTypes'
|
||||
|
||||
import Debug from 'debug'
|
||||
const debug = Debug('lot-occupancy-system:www')
|
||||
const debug = Debug(`lot-occupancy-system:www:${process.pid}`)
|
||||
|
||||
const directoryName = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
|
|
@ -22,7 +24,11 @@ const processCount = Math.min(
|
|||
os.cpus().length
|
||||
)
|
||||
|
||||
process.title =
|
||||
configFunctions.getProperty('application.applicationName') + ' (Primary)'
|
||||
|
||||
debug(`Primary pid: ${process.pid}`)
|
||||
debug(`Primary title: ${process.title}`)
|
||||
debug(`Launching ${processCount} processes`)
|
||||
|
||||
const clusterSettings = {
|
||||
|
|
@ -31,12 +37,30 @@ const clusterSettings = {
|
|||
|
||||
cluster.setupPrimary(clusterSettings)
|
||||
|
||||
const activeWorkers = new Map<number, any>()
|
||||
|
||||
for (let index = 0; index < processCount; index += 1) {
|
||||
cluster.fork()
|
||||
const worker = cluster.fork()
|
||||
activeWorkers.set(worker.process.pid!, worker)
|
||||
}
|
||||
|
||||
cluster.on('message', (worker, message: WorkerMessage) => {
|
||||
if (message?.messageType === 'clearCache') {
|
||||
for (const [pid, worker] of activeWorkers.entries()) {
|
||||
if (worker === undefined || pid === message.pid) {
|
||||
continue
|
||||
}
|
||||
|
||||
debug('Relaying message to workers')
|
||||
worker.send(message)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
cluster.on('exit', (worker, code, signal) => {
|
||||
debug(`Worker ${worker.process.pid!.toString()} has been killed`)
|
||||
activeWorkers.delete(worker.process.pid!)
|
||||
|
||||
debug('Starting another worker')
|
||||
cluster.fork()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ function onListening(server) {
|
|||
debug('HTTP Listening on ' + bind);
|
||||
}
|
||||
}
|
||||
process.title = configFunctions.getProperty('application.applicationName') + ' (Worker)';
|
||||
const httpPort = configFunctions.getProperty('application.httpPort');
|
||||
const httpServer = http.createServer(app);
|
||||
httpServer.listen(httpPort);
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ function onListening(server: http.Server): void {
|
|||
* Initialize HTTP
|
||||
*/
|
||||
|
||||
process.title = configFunctions.getProperty('application.applicationName') + ' (Worker)'
|
||||
|
||||
const httpPort = configFunctions.getProperty('application.httpPort')
|
||||
|
||||
const httpServer = http.createServer(app)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
[Home](https://cityssm.github.io/lot-occupancy-system/)
|
||||
•
|
||||
[Help](https://cityssm.github.io/lot-occupancy-system/docs/)
|
||||
|
||||
# Cemetery Management System Workflow - Newly Deceased
|
||||
|
||||
_The following workflow describes a process that can be used when the Lot Occupancy System is used
|
||||
as a Cemetery Management System._
|
||||
|
||||
## Step 1: Search for a Related Preneed Occupancy Record
|
||||
|
||||

|
||||
|
||||
If the deceased purchased preneed services, find them now.
|
||||
|
||||

|
||||
|
||||
It is important to note what services have been paid for,
|
||||
and who is entitled to the services.
|
||||
|
||||
## Step 2: Create the New Interment or Cremation Occupancy Record
|
||||
|
||||

|
||||
|
||||
If a preneed occupancy record exists, you can save time by copying the preneed record as a new record.
|
||||
|
||||
If no preneed occupancy record exists, a new occupancy record should be created.
|
||||
|
||||
## Step 3: Create a Work Order Associated with the Occupancy Record
|
||||
|
||||
Ensure the necessary milestones are included.
|
||||
|
||||
## Step 4: Complete the Work Order
|
||||
|
|
@ -18,4 +18,4 @@ export declare function getWorkOrderTypeById(workOrderTypeId: number): Promise<r
|
|||
export declare function getWorkOrderMilestoneTypes(): Promise<recordTypes.WorkOrderMilestoneType[]>;
|
||||
export declare function getWorkOrderMilestoneTypeById(workOrderMilestoneTypeId: number): Promise<recordTypes.WorkOrderMilestoneType | undefined>;
|
||||
export declare function getWorkOrderMilestoneTypeByWorkOrderMilestoneType(workOrderMilestoneTypeString: string): Promise<recordTypes.WorkOrderMilestoneType | undefined>;
|
||||
export declare function clearCacheByTableName(tableName: string): void;
|
||||
export declare function clearCacheByTableName(tableName: string, relayMessage?: boolean): void;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import cluster from 'node:cluster';
|
||||
import * as configFunctions from './functions.config.js';
|
||||
import { getLotOccupantTypes as getLotOccupantTypesFromDatabase } from './lotOccupancyDB/getLotOccupantTypes.js';
|
||||
import { getLotStatuses as getLotStatusesFromDatabase } from './lotOccupancyDB/getLotStatuses.js';
|
||||
|
|
@ -6,9 +7,8 @@ import { getOccupancyTypes as getOccupancyTypesFromDatabase } from './lotOccupan
|
|||
import { getOccupancyTypeFields as getOccupancyTypeFieldsFromDatabase } from './lotOccupancyDB/getOccupancyTypeFields.js';
|
||||
import { getWorkOrderTypes as getWorkOrderTypesFromDatabase } from './lotOccupancyDB/getWorkOrderTypes.js';
|
||||
import { getWorkOrderMilestoneTypes as getWorkOrderMilestoneTypesFromDatabase } from './lotOccupancyDB/getWorkOrderMilestoneTypes.js';
|
||||
import { getConfigTableMaxTimeMillis } from './lotOccupancyDB/getConfigTableMaxTimeMillis.js';
|
||||
import { setIntervalAsync, clearIntervalAsync } from 'set-interval-async';
|
||||
import { asyncExitHook } from 'exit-hook';
|
||||
import Debug from 'debug';
|
||||
const debug = Debug(`lot-occupancy-system:functions.cache:${process.pid}`);
|
||||
let lotOccupantTypes;
|
||||
export async function getLotOccupantTypes() {
|
||||
if (lotOccupantTypes === undefined) {
|
||||
|
|
@ -163,7 +163,7 @@ export async function getWorkOrderMilestoneTypeByWorkOrderMilestoneType(workOrde
|
|||
function clearWorkOrderMilestoneTypesCache() {
|
||||
workOrderMilestoneTypes = undefined;
|
||||
}
|
||||
export function clearCacheByTableName(tableName) {
|
||||
export function clearCacheByTableName(tableName, relayMessage = true) {
|
||||
switch (tableName) {
|
||||
case 'LotOccupantTypes': {
|
||||
clearLotOccupantTypesCache();
|
||||
|
|
@ -193,26 +193,23 @@ export function clearCacheByTableName(tableName) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
function clearAllCaches() {
|
||||
clearLotOccupantTypesCache();
|
||||
clearLotStatusesCache();
|
||||
clearLotTypesCache();
|
||||
clearOccupancyTypesCache();
|
||||
clearWorkOrderMilestoneTypesCache();
|
||||
clearWorkOrderTypesCache();
|
||||
}
|
||||
let configTimeMillis = 0;
|
||||
async function checkCacheIntegrity() {
|
||||
const timeMillis = await getConfigTableMaxTimeMillis();
|
||||
if (timeMillis > configTimeMillis) {
|
||||
configTimeMillis = timeMillis;
|
||||
clearAllCaches();
|
||||
try {
|
||||
if (relayMessage && cluster.isWorker) {
|
||||
const workerMessage = {
|
||||
messageType: 'clearCache',
|
||||
tableName,
|
||||
timeMillis: Date.now(),
|
||||
pid: process.pid
|
||||
};
|
||||
debug(`Sending clear cache from worker: ${tableName}`);
|
||||
process.send(workerMessage);
|
||||
}
|
||||
}
|
||||
const cacheTimer = setIntervalAsync(checkCacheIntegrity, 10 * 60 * 1000);
|
||||
asyncExitHook(async () => {
|
||||
await clearIntervalAsync(cacheTimer);
|
||||
}, {
|
||||
minimumWait: 250
|
||||
catch { }
|
||||
}
|
||||
process.on('message', (message) => {
|
||||
if (message.messageType === 'clearCache' && message.pid !== process.pid) {
|
||||
debug(`Clearing cache: ${message.tableName}`);
|
||||
clearCacheByTableName(message.tableName, false);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/indent */
|
||||
|
||||
import cluster from 'node:cluster'
|
||||
|
||||
import * as configFunctions from './functions.config.js'
|
||||
|
||||
import { getLotOccupantTypes as getLotOccupantTypesFromDatabase } from './lotOccupancyDB/getLotOccupantTypes.js'
|
||||
|
|
@ -15,12 +17,11 @@ import { getWorkOrderTypes as getWorkOrderTypesFromDatabase } from './lotOccupan
|
|||
|
||||
import { getWorkOrderMilestoneTypes as getWorkOrderMilestoneTypesFromDatabase } from './lotOccupancyDB/getWorkOrderMilestoneTypes.js'
|
||||
|
||||
import { getConfigTableMaxTimeMillis } from './lotOccupancyDB/getConfigTableMaxTimeMillis.js'
|
||||
|
||||
import { setIntervalAsync, clearIntervalAsync } from 'set-interval-async'
|
||||
import { asyncExitHook } from 'exit-hook'
|
||||
|
||||
import type * as recordTypes from '../types/recordTypes'
|
||||
import type { WorkerMessage } from '../types/applicationTypes'
|
||||
|
||||
import Debug from 'debug'
|
||||
const debug = Debug(`lot-occupancy-system:functions.cache:${process.pid}`)
|
||||
|
||||
/*
|
||||
* Lot Occupant Types
|
||||
|
|
@ -301,7 +302,10 @@ function clearWorkOrderMilestoneTypesCache(): void {
|
|||
workOrderMilestoneTypes = undefined
|
||||
}
|
||||
|
||||
export function clearCacheByTableName(tableName: string): void {
|
||||
export function clearCacheByTableName(
|
||||
tableName: string,
|
||||
relayMessage = true
|
||||
): void {
|
||||
switch (tableName) {
|
||||
case 'LotOccupantTypes': {
|
||||
clearLotOccupantTypesCache()
|
||||
|
|
@ -336,39 +340,26 @@ export function clearCacheByTableName(tableName: string): void {
|
|||
break
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (relayMessage && cluster.isWorker) {
|
||||
const workerMessage: WorkerMessage = {
|
||||
messageType: 'clearCache',
|
||||
tableName,
|
||||
timeMillis: Date.now(),
|
||||
pid: process.pid
|
||||
}
|
||||
|
||||
function clearAllCaches(): void {
|
||||
clearLotOccupantTypesCache()
|
||||
clearLotStatusesCache()
|
||||
clearLotTypesCache()
|
||||
clearOccupancyTypesCache()
|
||||
clearWorkOrderMilestoneTypesCache()
|
||||
clearWorkOrderTypesCache()
|
||||
debug(`Sending clear cache from worker: ${tableName}`)
|
||||
|
||||
process.send!(workerMessage)
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/*
|
||||
* Config Time Millis
|
||||
*/
|
||||
|
||||
let configTimeMillis = 0
|
||||
|
||||
async function checkCacheIntegrity(): Promise<void> {
|
||||
const timeMillis = await getConfigTableMaxTimeMillis()
|
||||
|
||||
if (timeMillis > configTimeMillis) {
|
||||
configTimeMillis = timeMillis
|
||||
clearAllCaches()
|
||||
process.on('message', (message: WorkerMessage) => {
|
||||
if (message.messageType === 'clearCache' && message.pid !== process.pid) {
|
||||
debug(`Clearing cache: ${message.tableName}`)
|
||||
clearCacheByTableName(message.tableName, false)
|
||||
}
|
||||
}
|
||||
|
||||
const cacheTimer = setIntervalAsync(checkCacheIntegrity, 10 * 60 * 1000)
|
||||
|
||||
asyncExitHook(
|
||||
async () => {
|
||||
await clearIntervalAsync(cacheTimer)
|
||||
},
|
||||
{
|
||||
minimumWait: 250
|
||||
}
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
export declare function getConfigTableMaxTimeMillis(): Promise<number>;
|
||||
export default getConfigTableMaxTimeMillis;
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import { acquireConnection } from './pool.js';
|
||||
export async function getConfigTableMaxTimeMillis() {
|
||||
const database = await acquireConnection();
|
||||
const result = database
|
||||
.prepare(`select max(timeMillis) as timeMillis from (
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from LotOccupantTypes
|
||||
UNION
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from LotStatuses
|
||||
UNION
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from LotTypes
|
||||
UNION
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from OccupancyTypes
|
||||
UNION
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from OccupancyTypeFields
|
||||
UNION
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from OccupancyTypePrints
|
||||
UNION
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from WorkOrderTypes
|
||||
UNION
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from WorkOrderMilestoneTypes
|
||||
)`)
|
||||
.get();
|
||||
database.release();
|
||||
return result?.timeMillis ?? 0;
|
||||
}
|
||||
export default getConfigTableMaxTimeMillis;
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
import { acquireConnection } from './pool.js'
|
||||
|
||||
export async function getConfigTableMaxTimeMillis(): Promise<number> {
|
||||
const database = await acquireConnection()
|
||||
|
||||
const result = database
|
||||
.prepare(
|
||||
`select max(timeMillis) as timeMillis from (
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from LotOccupantTypes
|
||||
UNION
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from LotStatuses
|
||||
UNION
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from LotTypes
|
||||
UNION
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from OccupancyTypes
|
||||
UNION
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from OccupancyTypeFields
|
||||
UNION
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from OccupancyTypePrints
|
||||
UNION
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from WorkOrderTypes
|
||||
UNION
|
||||
select max(max(recordUpdate_timeMillis, ifnull(recordDelete_timeMillis,0))) as timeMillis
|
||||
from WorkOrderMilestoneTypes
|
||||
)`
|
||||
)
|
||||
.get()
|
||||
|
||||
database.release()
|
||||
|
||||
return result?.timeMillis ?? 0
|
||||
}
|
||||
|
||||
export default getConfigTableMaxTimeMillis
|
||||
|
|
@ -8,8 +8,8 @@
|
|||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env NODE_ENV=production node ./bin/www",
|
||||
"dev:test": "cross-env NODE_ENV=dev DEBUG=lot-occupancy-system:*,dynamics-gp:* TEST_DATABASES=true nodemon ./bin/www.js",
|
||||
"start": "cross-env NODE_ENV=production node ./bin/www.js",
|
||||
"dev:test": "cross-env NODE_ENV=dev DEBUG=lot-occupancy-system:*,dynamics-gp:* TEST_DATABASES=true nodemon --inspect ./bin/www.js",
|
||||
"dev:test:process": "cross-env NODE_ENV=dev DEBUG=lot-occupancy-system:* TEST_DATABASES=true nodemon ./bin/wwwProcess.js",
|
||||
"dev:live": "cross-env NODE_ENV=dev DEBUG=lot-occupancy-system:* nodemon ./bin/www.js",
|
||||
"cy:open": "cypress open --config-file cypress.config.js",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@import '@cityssm/bulma-webapp-css/cityssm';
|
||||
@import 'bulma/sass/utilities/derived-variables';
|
||||
@import 'bulma-calendar/src/sass/index';
|
||||
@import 'bulma-calendar/src/sass';
|
||||
@import '@cityssm/fa-glow/fa-glow';
|
||||
|
||||
$white: #fff;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
export interface WorkerMessage {
|
||||
messageType: 'clearCache';
|
||||
tableName: string;
|
||||
timeMillis: number;
|
||||
pid: number;
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export interface WorkerMessage {
|
||||
messageType: 'clearCache'
|
||||
tableName: string
|
||||
timeMillis: number
|
||||
pid: number
|
||||
}
|
||||
Loading…
Reference in New Issue