Garbage Collector Update (#22428)

* Garbage Collector Update

* doc
This commit is contained in:
Fox McCloud
2023-10-02 20:10:32 +01:00
committed by GitHub
parent 9d0c74b687
commit 3b3082c844
6 changed files with 90 additions and 44 deletions
+54 -39
View File
@@ -8,7 +8,7 @@ SUBSYSTEM_DEF(garbage)
offline_implications = "Garbage collection is no longer functional, and objects will not be qdel'd. Immediate server restart recommended."
cpu_display = SS_CPUDISPLAY_HIGH
var/list/collection_timeout = list(2 MINUTES, 10 SECONDS) // deciseconds to wait before moving something up in the queue to the next level
var/list/collection_timeout = list(GC_FILTER_QUEUE, GC_CHECK_QUEUE, GC_DEL_QUEUE) // deciseconds to wait before moving something up in the queue to the next level
//Stat tracking
var/delslasttick = 0 // number of del()'s we've done this tick
@@ -34,13 +34,7 @@ SUBSYSTEM_DEF(garbage)
/datum/controller/subsystem/garbage/PreInit()
queues = new(GC_QUEUE_COUNT)
pass_counts = new(GC_QUEUE_COUNT)
fail_counts = new(GC_QUEUE_COUNT)
for(var/i in 1 to GC_QUEUE_COUNT)
queues[i] = list()
pass_counts[i] = 0
fail_counts[i] = 0
InitQueues()
/datum/controller/subsystem/garbage/get_stat_details()
var/list/msg = list()
@@ -105,10 +99,13 @@ SUBSYSTEM_DEF(garbage)
/datum/controller/subsystem/garbage/fire()
//the fact that this resets its processing each fire (rather then resume where it left off) is intentional.
var/queue = GC_QUEUE_CHECK
var/queue = GC_QUEUE_FILTER
while(state == SS_RUNNING)
switch(queue)
if(GC_QUEUE_FILTER)
HandleQueue(GC_QUEUE_FILTER)
queue = GC_QUEUE_FILTER + 1
if(GC_QUEUE_CHECK)
HandleQueue(GC_QUEUE_CHECK)
queue = GC_QUEUE_CHECK + 1
@@ -118,11 +115,20 @@ SUBSYSTEM_DEF(garbage)
state = SS_RUNNING
break
/datum/controller/subsystem/garbage/proc/InitQueues()
if(isnull(queues)) // Only init the queues if they don't already exist, prevents overriding of recovered lists
queues = new(GC_QUEUE_COUNT)
pass_counts = new(GC_QUEUE_COUNT)
fail_counts = new(GC_QUEUE_COUNT)
for(var/i in 1 to GC_QUEUE_COUNT)
queues[i] = list()
pass_counts[i] = 0
fail_counts[i] = 0
#define IS_DELETED(datum, gcd_at_time) (isnull(##datum) || ##datum.gc_destroyed != gcd_at_time)
/datum/controller/subsystem/garbage/proc/HandleQueue(level = GC_QUEUE_CHECK)
if(level == GC_QUEUE_CHECK)
/datum/controller/subsystem/garbage/proc/HandleQueue(level = GC_QUEUE_FILTER)
if(level == GC_QUEUE_FILTER)
delslasttick = 0
gcedlasttick = 0
var/cut_off_time = world.time - collection_timeout[level] //ignore entries newer then this
@@ -137,33 +143,33 @@ SUBSYSTEM_DEF(garbage)
lastlevel = level
// The instinct is to use a for in loop here, to walk the entries in the queue
// The trouble is this performs a copy of the queue list, and since this can in theory balloon a LOT
// It's better to just go index by index. It's not a huge deal but it's worth doin IMO
//We do this rather then for(var/refID in queue) because that sort of for loop copies the whole list.
//Normally this isn't expensive, but the gc queue can grow to 40k items, and that gets costly/causes overrun.
for(var/i in 1 to length(queue))
var/list/packet = queue[i]
if(length(packet) != 2)
var/list/L = queue[i]
if(length(L) < GC_QUEUE_ITEM_INDEX_COUNT)
count++
if(MC_TICK_CHECK)
return
continue
var/GCd_at_time = packet[2]
if(GCd_at_time > cut_off_time)
var/queued_at_time = L[GC_QUEUE_ITEM_QUEUE_TIME]
if(queued_at_time > cut_off_time)
break // Everything else is newer, skip them
count++
var/refID = packet[1]
var/GCd_at_time = L[GC_QUEUE_ITEM_GCD_DESTROYED]
var/refID = L[GC_QUEUE_ITEM_REF]
var/datum/D
D = locate(refID)
if(!D || D.gc_destroyed != GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake
if(IS_DELETED(D, GCd_at_time)) // So if something else coincidently gets the same ref, it's not deleted by mistake
++gcedlasttick
++totalgcs
pass_counts[level]++
#ifdef REFERENCE_TRACKING
reference_find_on_fail -= refID //It's deleted we don't care anymore.
reference_find_on_fail -= text_ref(D) //It's deleted we don't care anymore.
#endif
if(MC_TICK_CHECK)
return
@@ -171,26 +177,28 @@ SUBSYSTEM_DEF(garbage)
// Something's still referring to the qdel'd object.
fail_counts[level]++
#ifdef REFERENCE_TRACKING
var/ref_searching = FALSE
#endif
switch(level)
if(GC_QUEUE_CHECK)
#ifdef REFERENCE_TRACKING
if(reference_find_on_fail[refID] && !ref_search_stop)
if(reference_find_on_fail[text_ref(D)] && !ref_search_stop)
INVOKE_ASYNC(D, TYPE_PROC_REF(/datum, find_references))
ref_searching = TRUE
#ifdef GC_FAILURE_HARD_LOOKUP
else if (!ref_search_stop)
else if(!ref_search_stop)
INVOKE_ASYNC(D, TYPE_PROC_REF(/datum, find_references))
ref_searching = TRUE
#endif
reference_find_on_fail -= refID
reference_find_on_fail -= text_ref(D)
#endif
var/type = D.type
var/datum/qdel_item/I = items[type]
#ifdef REFERENCE_TRACKING
log_gc("GC: -- \ref[src] | [type] was unable to be GC'd --")
log_gc("GC: -- [text_ref(D)] | [type] was unable to be GC'd --")
#endif
I.failures++
if(GC_QUEUE_HARDDELETE)
@@ -212,19 +220,25 @@ SUBSYSTEM_DEF(garbage)
queue.Cut(1, count + 1)
count = 0
/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_CHECK)
#undef IS_DELETED
/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_FILTER)
if(isnull(D))
return
if(level > GC_QUEUE_COUNT)
HardDelete(D)
return
var/gctime = world.time
var/queue_time = world.time
D.gc_destroyed = gctime
var/refid = text_ref(D)
var/static/uid = 0
if(D.gc_destroyed <= 0)
uid = WRAP(uid + 1, 1, SHORT_REAL_LIMIT - 1)
D.gc_destroyed = uid
var/list/queue = queues[level]
// I hate byond lists so much man
queue[++queue.len] = list("\ref[D]", gctime)
queue[++queue.len] = list(queue_time, refid, D.gc_destroyed) // not += for byond reasons
//this is mainly to separate things profile wise.
/datum/controller/subsystem/garbage/proc/HardDelete(datum/D)
@@ -234,7 +248,7 @@ SUBSYSTEM_DEF(garbage)
++delslasttick
++totaldels
var/type = D.type
var/refID = "\ref[D]"
var/refID = text_ref(D)
del(D)
@@ -259,6 +273,7 @@ SUBSYSTEM_DEF(garbage)
postpone(time)
/datum/controller/subsystem/garbage/Recover()
InitQueues() //We first need to create the queues before recovering data
if(istype(SSgarbage.queues))
for(var/i in 1 to SSgarbage.queues.len)
queues[i] |= SSgarbage.queues[i]
@@ -283,7 +298,7 @@ SUBSYSTEM_DEF(garbage)
thing_to_del.qdel_and_find_ref_if_fail(force)
/datum/proc/qdel_and_find_ref_if_fail(force = FALSE)
SSgarbage.reference_find_on_fail["\ref[src]"] = TRUE
SSgarbage.reference_find_on_fail[text_ref(D)] = TRUE
qdel(src, force)
#endif
@@ -294,12 +309,12 @@ SUBSYSTEM_DEF(garbage)
if(!istype(D))
del(D)
return
var/datum/qdel_item/I = SSgarbage.items[D.type]
if(!I)
I = SSgarbage.items[D.type] = new /datum/qdel_item(D.type)
I.qdels++
if(isnull(D.gc_destroyed))
if(SEND_SIGNAL(D, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
return
@@ -349,7 +364,7 @@ SUBSYSTEM_DEF(garbage)
if(QDEL_HINT_IFFAIL_FINDREFERENCE)
SSgarbage.Queue(D)
#ifdef REFERENCE_TRACKING
SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE
SSgarbage.reference_find_on_fail[text_ref(D)] = TRUE
#endif
else
#ifdef REFERENCE_TRACKING
@@ -474,11 +489,11 @@ SUBSYSTEM_DEF(garbage)
var/variable = vars_list[varname]
if(variable == src)
log_gc("Found [type] \ref[src] in [datum_container.type]'s \ref[datum_container] [varname] var. [container_name]")
log_gc("Found [type] [text_ref(src)] in [datum_container.type]'s [text_ref(datum_container)] [varname] var. [container_name]")
continue
if(islist(variable))
DoSearchVar(variable, "[container_name] \ref[datum_container] -> [varname] (list)", recursive_limit - 1, search_time)
DoSearchVar(variable, "[container_name] [text_ref(datum_container)] -> [varname] (list)", recursive_limit - 1, search_time)
else if(islist(potential_container))
var/normal = IS_NORMAL_LIST(potential_container)
@@ -489,7 +504,7 @@ SUBSYSTEM_DEF(garbage)
#endif
//Check normal entrys
if(element_in_list == src)
log_gc("Found [type] \ref[src] in list [container_name].")
log_gc("Found [type] [text_ref(src)] in list [container_name].")
continue
var/assoc_val = null
@@ -497,7 +512,7 @@ SUBSYSTEM_DEF(garbage)
assoc_val = potential_cache[element_in_list]
//Check assoc entrys
if(assoc_val == src)
log_gc("Found [type] \ref[src] in list [container_name]\[[element_in_list]\]")
log_gc("Found [type] [text_ref(src)] in list [container_name]\[[element_in_list]\]")
continue
//We need to run both of these checks, since our object could be hiding in either of them
//Check normal sublists