Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance Timer/Cache/CachedValues API #399

Merged
merged 14 commits into from
Sep 16, 2024
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,20 @@

## 22.0-SNAPSHOT

### 💥 Breaking Changes (upgrade difficulty: 🟢 LOW)
* All `Timer`, `Cache`, and `CachedValue` object require a 'name' property. This property was
previously optional in many cases, but is now required in order to support new cluster features,
logging, and admin tools. The new `BaseService.resources` property now will give access to all
resources by name, if needed and replaces `BaseService.timers`.

### 🎁 New Features
* `Cache` and `CachedValue` may now be created using a factory on `BaseService`. This streamlined
interface reduces boilerplate, and provides a consistent interface with `Timer`.

### ⚙️ Technical

* Improvements to `Timer` to avoid extra executions when primary instance changes.
lbwexler marked this conversation as resolved.
Show resolved Hide resolved

* Updated `ClusterService` to use Hoist's `InstanceNotFoundException` class to designate routine.

* Exposed `/xh/ping` as whitelisted route for basic uptime/reachability checks. Retained legacy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ class ConnectionPoolMonitoringService extends BaseService {

void init() {
createTimer(
interval: {enabled ? config.snapshotInterval * DateTimeUtils.SECONDS: -1},
runFn: this.&takeSnapshot
name: 'takeSnapshot',
runFn: this.&takeSnapshot,
interval: {enabled ? config.snapshotInterval * DateTimeUtils.SECONDS: -1}
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ class MemoryMonitoringService extends BaseService {

void init() {
createTimer(
interval: {this.enabled ? config.snapshotInterval * DateTimeUtils.SECONDS: -1},
runFn: this.&takeSnapshot
name: 'takeSnapshot',
runFn: this.&takeSnapshot,
interval: {this.enabled ? config.snapshotInterval * DateTimeUtils.SECONDS: -1}
)
}

Expand Down Expand Up @@ -178,6 +179,6 @@ class MemoryMonitoringService extends BaseService {

Map getAdminStats() {[
config: configForAdminStats('xhMemoryMonitoringConfig'),
latestSnapshot: latestSnapshot,
latestSnapshot: latestSnapshot
]}
}
40 changes: 20 additions & 20 deletions grails-app/services/io/xh/hoist/admin/ServiceManagerService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package io.xh.hoist.admin

import com.hazelcast.core.DistributedObject
import io.xh.hoist.BaseService

class ServiceManagerService extends BaseService {
Expand All @@ -15,8 +16,6 @@ class ServiceManagerService extends BaseService {
clusterAdminService

Collection<Map> listServices() {


getServicesInternal().collect { name, svc ->
return [
name: name,
Expand All @@ -28,24 +27,8 @@ class ServiceManagerService extends BaseService {

Map getStats(String name) {
def svc = grailsApplication.mainContext.getBean(name),
prefix = svc.class.name + '_',
timers = svc.timers*.adminStats,
distObjs = clusterService.distributedObjects
.findAll { it.getName().startsWith(prefix) }
.collect {clusterAdminService.getAdminStatsForObject(it)}

Map ret = svc.adminStats
if (timers || distObjs) {
ret = ret.clone()
if (distObjs) ret.distributedObjects = distObjs
if (timers.size() == 1) {
ret.timer = timers[0]
} else if (timers.size() > 1) {
ret.timers = timers
}
}

return ret
resources = getResourceStats(svc)
return resources ? [*: svc.adminStats, resources: resources] : svc.adminStats
}

void clearCaches(List<String> names) {
Expand All @@ -60,6 +43,23 @@ class ServiceManagerService extends BaseService {
}
}

//----------------------
// Implementation
//----------------------
private List getResourceStats(BaseService svc) {
svc.resources
.findAll { !it.key.startsWith('xh_') } // skip hoist implementation objects
.collect { k, v ->
Map stats = v instanceof DistributedObject ?
clusterAdminService.getAdminStatsForObject(v) :
v.adminStats

// rely on the name (key) service knows, i.e avoid HZ prefix
return [*: stats, name: k]
}
}


private Map<String, BaseService> getServicesInternal() {
return grailsApplication.mainContext.getBeansOfType(BaseService.class, false, false)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,14 @@ class AlertBannerService extends BaseService {
private final static String presetsBlobName = 'xhAlertBannerPresets'

private final Map emptyAlert = [active: false]
private CachedValue<Map> _alertBanner = new CachedValue<>(
name: 'alertBanner',
replicate: true,
svc: this
)
private CachedValue<Map> _alertBanner = createCachedValue(name: 'alertBanner', replicate: true)
private Timer timer

void init() {
timer = createTimer(
interval: 2 * MINUTES,
name: 'readFromSpec',
runFn: this.&readFromSpec,
interval: 2 * MINUTES,
primaryOnly: true
)
super.init()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class ClientErrorService extends BaseService {
void init() {
super.init()
createTimer(
name: 'processErrors',
runFn: this.&processErrors,
interval: { alertInterval },
delay: 15 * SECONDS,
primaryOnly: true
Expand Down Expand Up @@ -99,7 +101,7 @@ class ClientErrorService extends BaseService {
// Implementation
//---------------------------------------------------------
@Transactional
void onTimer() {
private void processErrors() {
lbwexler marked this conversation as resolved.
Show resolved Hide resolved
if (!errors) return

def maxErrors = getMaxErrors(),
Expand All @@ -121,8 +123,7 @@ class ClientErrorService extends BaseService {
}

Map getAdminStats() {[
config: configForAdminStats('xhClientErrorConfig'),
pendingErrorCount: errors.size()
config: configForAdminStats('xhClientErrorConfig')
]}

}
3 changes: 1 addition & 2 deletions grails-app/services/io/xh/hoist/config/ConfigService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,7 @@ class ConfigService extends BaseService {
}

void fireConfigChanged(AppConfig obj) {
def topic = clusterService.getTopic('xhConfigChanged')
topic.publishAsync([key: obj.name, value: obj.externalValue()])
getTopic('xhConfigChanged').publishAsync([key: obj.name, value: obj.externalValue()])
lbwexler marked this conversation as resolved.
Show resolved Hide resolved
}

//-------------------
Expand Down
6 changes: 3 additions & 3 deletions grails-app/services/io/xh/hoist/ldap/LdapService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ class LdapService extends BaseService {

def configService

private Cache<String, List<LdapObject>> cache = new Cache<>(
expireTime: {config.cacheExpireSecs * SECONDS},
svc: this
private Cache<String, List<LdapObject>> cache = createCache(
name: 'queryCache',
expireTime: {config.cacheExpireSecs * SECONDS}
)

static clearCachesConfigs = ['xhLdapConfig', 'xhLdapUsername', 'xhLdapPassword']
Expand Down
12 changes: 5 additions & 7 deletions grails-app/services/io/xh/hoist/log/LogArchiveService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ class LogArchiveService extends BaseService {
logReaderService

void init() {
createTimer(interval: 1 * DAYS)
createTimer(
name: 'archiveLogs',
runFn: { archiveLogs((Integer) config.archiveAfterDays)},
interval: 1 * DAYS
)
}

List<String> archiveLogs(Integer daysThreshold) {
Expand Down Expand Up @@ -69,12 +73,6 @@ class LogArchiveService extends BaseService {
//------------------------
// Implementation
//------------------------
private void onTimer() {
if (isPrimary) {
lbwexler marked this conversation as resolved.
Show resolved Hide resolved
archiveLogs((Integer) config.archiveAfterDays)
}
}

private File getArchiveDir(String logPath, String category) {
return new File(logPath + separator + config.archiveFolder + separator + category)
}
Expand Down
11 changes: 6 additions & 5 deletions grails-app/services/io/xh/hoist/log/LogLevelService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ class LogLevelService extends BaseService {
private List<LogLevelAdjustment> adjustments = []

void init() {
createTimer(interval: 30 * MINUTES, runImmediatelyAndBlock: true)
}

private void onTimer() {
calculateAdjustments()
createTimer(
name: 'calculateAdjustments',
runFn: this.&calculateAdjustments,
interval: 30 * MINUTES,
runImmediatelyAndBlock: true
)
}

// -------------------------------------------------------------------------------
Expand Down
11 changes: 6 additions & 5 deletions grails-app/services/io/xh/hoist/monitor/MonitorService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,18 @@ class MonitorService extends BaseService {

// Shared state for all servers to read - gathered by primary from all instances.
// Map of monitor code to aggregated (cross-instance) results.
private CachedValue<Map<String, AggregateMonitorResult>> _results = new CachedValue<>(
private CachedValue<Map<String, AggregateMonitorResult>> _results = createCachedValue(
name: 'results',
replicate: true,
svc: this
replicate: true
)

private Timer timer

void init() {
timer = createTimer(
interval: { monitorInterval },
name: 'runMonitors',
runFn: this.&runMonitors,
interval: {monitorInterval},
delay: startupDelay,
primaryOnly: true
)
Expand Down Expand Up @@ -86,7 +87,7 @@ class MonitorService extends BaseService {
//------------------
// Implementation
//------------------
private void onTimer() {
private void runMonitors() {
// Gather per-instance results from across the cluster
Map<String, List<MonitorResult>> newChecks = clusterService
.submitToAllInstances(new RunAllMonitorsTask())
Expand Down
Loading
Loading