fix(apiserver): fall back to default HostConfig for unmatched workers (bug-1673, gitea #40)
OnHostConfigUpdated only applied config-sourced fields (MaxSlot, HostModel, HostType, DisptWindow) onto a host whose name exact-matched an entry already in the store. hosts.yaml commonly defines only a wildcard "default" entry, so a dynamically-joined worker never received its own config push and MaxSlot stayed at Go's zero-value forever -- decoupled from the C-side scheduling path, which already resolves the real value via worker.c's wildcard fallback. The "default" pseudo-host's config is now cached and retroactively backfilled onto any registered host with no explicit config of its own, in either arrival order (config-before-worker or worker-before-config). The -1 HOSTCONFIG_MXJ_AUTO_DETECT sentinel (bug-1556) is resolved to the host's real NumCPUs when applied via fallback, mirroring worker.c's own resolution, so the display shows usable capacity rather than a bare -1. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AgYRU3zDTqNGV5Jk8aiKLg
This commit is contained in:
parent
88c7c05daf
commit
fe87dafc85
3 changed files with 239 additions and 1 deletions
|
|
@ -4,6 +4,7 @@ import (
|
|||
"apiserver/models"
|
||||
"apiserver/store"
|
||||
logger "apiserver/utils"
|
||||
"sync"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
|
@ -11,6 +12,66 @@ import (
|
|||
// DefaultHandler implements NotificationHandler by writing to stores.
|
||||
type DefaultHandler struct{}
|
||||
|
||||
// defaultHostConfigName is the cluster-wide config sentinel in hosts.yaml
|
||||
// (never a real worker) whose fields worker.c's apply_join_state()/
|
||||
// find_matched_hostconfig() already falls back to on the C side for any
|
||||
// worker with no host-specific hosts.yaml entry of its own.
|
||||
const defaultHostConfigName = "default"
|
||||
|
||||
// bug-1673: a dynamically-joined worker with no hosts.yaml entry under its
|
||||
// own literal hostname never receives its own OnHostConfigUpdated call, so
|
||||
// MaxSlot/HostModel/HostType/DisptWindow stayed at Go's zero-value forever
|
||||
// -- decoupled from the C-side scheduling path, which already resolves the
|
||||
// real value via the "default" wildcard entry. These package-level vars
|
||||
// cache that entry's fields plus which hosts have their OWN explicit
|
||||
// (non-default) config, so OnHostUpdated can apply the same fallback for
|
||||
// display purposes that worker.c already applies for real dispatch.
|
||||
var (
|
||||
hostConfigMu sync.Mutex
|
||||
defaultHostConfig *models.HostInfo
|
||||
hostsWithExplicitConfig = map[string]bool{}
|
||||
)
|
||||
|
||||
// resetHostConfigCache clears the cached "default" pseudo-host config and
|
||||
// explicit-config tracking. Test-only: called from this package's _test.go
|
||||
// store-reset helpers, so bug-1673 fallback state doesn't leak between
|
||||
// tests the way store.InitStores() already prevents for the stores
|
||||
// themselves.
|
||||
func resetHostConfigCache() {
|
||||
hostConfigMu.Lock()
|
||||
defaultHostConfig = nil
|
||||
hostsWithExplicitConfig = map[string]bool{}
|
||||
hostConfigMu.Unlock()
|
||||
}
|
||||
|
||||
// applyDefaultHostConfigFallback copies the cached "default" pseudo-host's
|
||||
// config-sourced fields onto host, if one has been observed yet. No-op
|
||||
// otherwise (nothing to fall back to).
|
||||
//
|
||||
// A negative MaxSlot is HOSTCONFIG_MXJ_AUTO_DETECT (bug-1556): "no explicit
|
||||
// cap, use this host's real CPU count" -- the same resolution worker.c's
|
||||
// apply_join_state()/find_matched_hostconfig() already performs for actual
|
||||
// dispatch ("if (nslots<0) nslots = maxcpus"). Resolving it here too means
|
||||
// this display fallback shows the host's real usable capacity (matching
|
||||
// what dispatch will actually honor) instead of a bare "-1" that would
|
||||
// leave the reporter's original "is ncpus equal slots?" question (gitea
|
||||
// #40) just as unanswered as the "0" it replaces.
|
||||
func applyDefaultHostConfigFallback(host *models.HostInfo) {
|
||||
hostConfigMu.Lock()
|
||||
def := defaultHostConfig
|
||||
hostConfigMu.Unlock()
|
||||
if def == nil {
|
||||
return
|
||||
}
|
||||
host.HostModel = def.HostModel
|
||||
host.HostType = def.HostType
|
||||
host.MaxSlot = def.MaxSlot
|
||||
if host.MaxSlot < 0 && host.NumCPUs > 0 {
|
||||
host.MaxSlot = host.NumCPUs
|
||||
}
|
||||
host.DisptWindow = def.DisptWindow
|
||||
}
|
||||
|
||||
func (h *DefaultHandler) OnJobCreated(job *models.JobInfo) {
|
||||
store.Jobs.Put(uint64(job.Unique_JobID), *job)
|
||||
}
|
||||
|
|
@ -42,7 +103,10 @@ func (h *DefaultHandler) OnQueueUpdated(queue *models.QueueInfo) {
|
|||
|
||||
// OnHostUpdated merges worker-sourced fields into an existing host entry.
|
||||
// Config-sourced fields (HostModel, HostType, MaxSlot, DisptWindow) are
|
||||
// preserved from the prior entry if one exists.
|
||||
// preserved from the prior entry if one exists. If this is the host's
|
||||
// first-ever registration (no prior entry), fall back to the cached
|
||||
// "default" pseudo-host's config (bug-1673) rather than leaving those
|
||||
// fields at Go's zero-value -- mirrors the C-side wildcard fallback.
|
||||
func (h *DefaultHandler) OnHostUpdated(incoming *models.HostInfo) {
|
||||
merged := *incoming
|
||||
if existing, ok := store.Hosts.Get(merged.HostName); ok {
|
||||
|
|
@ -50,6 +114,8 @@ func (h *DefaultHandler) OnHostUpdated(incoming *models.HostInfo) {
|
|||
merged.HostType = existing.HostType
|
||||
merged.MaxSlot = existing.MaxSlot
|
||||
merged.DisptWindow = existing.DisptWindow
|
||||
} else {
|
||||
applyDefaultHostConfigFallback(&merged)
|
||||
}
|
||||
store.Hosts.Put(merged.HostName, merged)
|
||||
}
|
||||
|
|
@ -57,7 +123,35 @@ func (h *DefaultHandler) OnHostUpdated(incoming *models.HostInfo) {
|
|||
// OnHostConfigUpdated applies config-sourced fields onto an existing worker-registered host.
|
||||
// Hosts not yet registered by a worker are skipped — config entries like "default" (which sets
|
||||
// cluster-wide defaults) or hosts whose daemons haven't started must not appear in the host list.
|
||||
//
|
||||
// The "default" pseudo-host entry itself is cached (bug-1673) rather than
|
||||
// simply discarded, then retroactively backfilled onto any already-
|
||||
// registered host with no explicit config of its own -- "default" can
|
||||
// arrive either before or after a given worker's own OnHostUpdated call.
|
||||
func (h *DefaultHandler) OnHostConfigUpdated(incoming *models.HostInfo) {
|
||||
if incoming.HostName == defaultHostConfigName {
|
||||
hostConfigMu.Lock()
|
||||
cp := *incoming
|
||||
defaultHostConfig = &cp
|
||||
hostConfigMu.Unlock()
|
||||
|
||||
store.Hosts.Range(func(name string, host models.HostInfo) bool {
|
||||
hostConfigMu.Lock()
|
||||
explicit := hostsWithExplicitConfig[name]
|
||||
hostConfigMu.Unlock()
|
||||
if !explicit {
|
||||
applyDefaultHostConfigFallback(&host)
|
||||
store.Hosts.Put(name, host)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
hostConfigMu.Lock()
|
||||
hostsWithExplicitConfig[incoming.HostName] = true
|
||||
hostConfigMu.Unlock()
|
||||
|
||||
existing, ok := store.Hosts.Get(incoming.HostName)
|
||||
if !ok {
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue