Updated SSgarbage (#18173)

* pain

* sadfas

* sdfa

* sdfasf

* sfa

* sdf

* might the lord have mercy on our soul

* i cri everidai

* adeste fideles

* sdf

* where will this lead, what's coming next, from your inventions

* dear lord

* gjvhk

* i cri everidai

* fsgf

* sdfa

* sdaf

* hiuhi
This commit is contained in:
Fluffy
2024-01-15 14:08:27 +00:00
committed by GitHub
parent 0c8bdbdd68
commit ca6b04e1d7
125 changed files with 1574 additions and 805 deletions
+8 -1
View File
@@ -13,7 +13,14 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
GLOB = src
var/datum/controller/exclude_these = new
gvars_datum_in_built_vars = exclude_these.vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order))
// I know this is dumb but the nested vars list hangs a ref to the datum. This fixes that
var/list/controller_vars = exclude_these.vars.Copy()
controller_vars["vars"] = null
gvars_datum_in_built_vars = controller_vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order))
#if MIN_COMPILER_VERSION >= 515 && MIN_COMPILER_BUILD > 1620
#warn datum.vars hanging a ref should now be fixed, there should be no reason to remove the vars list from our controller's vars list anymore
#endif
QDEL_IN(exclude_these, 0) //signal logging isn't ready
Initialize()
+344 -389
View File
@@ -1,39 +1,53 @@
/*!
## Debugging GC issues
In order to debug `qdel()` failures, there are several tools available.
To enable these tools, define `TESTING` in [_compile_options.dm](https://github.com/tgstation/-tg-station/blob/master/code/_compile_options.dm).
First is a verb called "Find References", which lists **every** refererence to an object in the world. This allows you to track down any indirect or obfuscated references that you might have missed.
Complementing this is another verb, "qdel() then Find References".
This does exactly what you'd expect; it calls `qdel()` on the object and then it finds all references remaining.
This is great, because it means that `Destroy()` will have been called before it starts to find references,
so the only references you'll find will be the ones preventing the object from `qdel()`ing gracefully.
If you have a datum or something you are not destroying directly (say via the singulo),
the next tool is `QDEL_HINT_FINDREFERENCE`. You can return this in `Destroy()` (where you would normally `return ..()`),
to print a list of references once it enters the GC queue.
Finally is a verb, "Show qdel() Log", which shows the deletion log that the garbage subsystem keeps. This is helpful if you are having race conditions or need to review the order of deletions.
Note that for any of these tools to work `TESTING` must be defined.
By using these methods of finding references, you can make your life far, far easier when dealing with `qdel()` failures.
*/
SUBSYSTEM_DEF(garbage)
name = "Garbage"
priority = SS_PRIORITY_GARBAGE
priority = FIRE_PRIORITY_GARBAGE
wait = 2 SECONDS
flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
init_order = INIT_ORDER_GARBAGE
init_stage = INITSTAGE_EARLY
var/collection_timeout = 3000// deciseconds to wait to let running procs finish before we just say fuck it and force del() the object
var/delslasttick = 0 // number of del()'s we've done this tick
var/gcedlasttick = 0 // number of things that gc'ed last tick
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
var/gcedlasttick = 0 // number of things that gc'ed last tick
var/totaldels = 0
var/totalgcs = 0
var/highest_del_time = 0
var/highest_del_tickusage = 0
var/highest_del_ms = 0
var/highest_del_type_string = ""
var/list/queue = list() // list of refID's of things that should be garbage collected
// refID's are associated with the time at which they time out and need to be manually del()
// we do this so we aren't constantly locating them and preventing them from being gc'd
var/list/pass_counts
var/list/fail_counts
var/list/tobequeued = list() //We store the references of things to be added to the queue seperately so we can spread out GC overhead over a few ticks
var/list/didntgc = list() // list of all types that have failed to GC associated with the number of times that's happened.
// the types are stored as strings
var/list/sleptDestroy = list() //Same as above but these are paths that slept during their Destroy call
var/list/noqdelhint = list()// list of all types that do not return a QDEL_HINT
// all types that did not respect qdel(A, force=TRUE) and returned one
// of the immortality qdel hints
var/list/noforcerespect = list()
#ifdef REFERENCE_TRACKING
var/list/qdel_list = list() // list of all types that have been qdel()eted
#endif
var/list/items = list() // Holds our qdel_item statistics datums
//Queue
var/list/queues
#ifdef REFERENCE_TRACKING
var/list/reference_find_on_fail = list()
#ifdef REFERENCE_TRACKING_DEBUG
@@ -42,8 +56,15 @@ SUBSYSTEM_DEF(garbage)
#endif
#endif
/datum/controller/subsystem/garbage/PreInit()
InitQueues()
/datum/controller/subsystem/garbage/stat_entry(msg)
msg = "W:[tobequeued.len]|Q:[queue.len]|D:[delslasttick]|G:[gcedlasttick]|"
var/list/counts = list()
for (var/list/L in queues)
counts += length(L)
msg += "Q:[counts.Join(",")]|D:[delslasttick]|G:[gcedlasttick]|"
msg += "GR:"
if (!(delslasttick+gcedlasttick))
msg += "n/a|"
@@ -55,405 +76,339 @@ SUBSYSTEM_DEF(garbage)
msg += "n/a|"
else
msg += "TGR:[round((totalgcs/(totaldels+totalgcs))*100, 0.01)]%"
msg += " P:[pass_counts.Join(",")]"
msg += "|F:[fail_counts.Join(",")]"
return ..()
/datum/controller/subsystem/garbage/Shutdown()
//Adds the del() log to the qdel log file
var/list/del_log = list()
//sort by how long it's wasted hard deleting
sortTim(items, cmp=/proc/cmp_qdel_item_time, associative = TRUE)
for(var/path in items)
var/datum/qdel_item/I = items[path]
var/list/entry = list()
del_log[path] = entry
if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
entry["SUSPENDED FOR LAG"] = TRUE
if (I.failures)
entry["Failures"] = I.failures
entry["qdel() Count"] = I.qdels
entry["Destroy() Cost (ms)"] = I.destroy_time
if (I.hard_deletes)
entry["Total Hard Deletes"] = I.hard_deletes
entry["Time Spend Hard Deleting (ms)"] = I.hard_delete_time
entry["Highest Time Spend Hard Deleting (ms)"] = I.hard_delete_max
if (I.hard_deletes_over_threshold)
entry["Hard Deletes Over Threshold"] = I.hard_deletes_over_threshold
if (I.slept_destroy)
entry["Total Sleeps"] = I.slept_destroy
if (I.no_respect_force)
entry["Total Ignored Force"] = I.no_respect_force
if (I.no_hint)
entry["Total No Hint"] = I.no_hint
if(LAZYLEN(I.extra_details))
entry["Deleted Metadata"] = I.extra_details
log_subsystem_garbage(json_encode(del_log))
/datum/controller/subsystem/garbage/fire()
HandleToBeQueued()
if(state == SS_RUNNING)
HandleQueue()
//the fact that this resets its processing each fire (rather then resume where it left off) is intentional.
var/queue = GC_QUEUE_FILTER
if (state == SS_PAUSED)
state = SS_RUNNING
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
if (GC_QUEUE_HARDDELETE)
HandleQueue(GC_QUEUE_HARDDELETE)
if (state == SS_PAUSED) //make us wait again before the next run.
state = SS_RUNNING
break
//If you see this proc high on the profile, what you are really seeing is the garbage collection/soft delete overhead in byond.
//Don't attempt to optimize, not worth the effort.
/datum/controller/subsystem/garbage/proc/HandleToBeQueued()
var/list/tobequeued = src.tobequeued
var/starttime = world.time
var/starttimeofday = world.timeofday
var/idex = 1
while((tobequeued.len - (idex - 1)) && starttime == world.time && starttimeofday == world.timeofday)
if (MC_TICK_CHECK)
break
var/ref = tobequeued[idex]
tobequeued[idex++] = null // Clear this ref to assist hard deletes in Queue().
Queue(ref)
if (idex > 1)
tobequeued.Cut(1, idex)
/datum/controller/subsystem/garbage/proc/HandleQueue()
delslasttick = 0
gcedlasttick = 0
var/time_to_kill = world.time - collection_timeout // Anything qdel() but not GC'd BEFORE this time needs to be manually del()
var/list/queue = src.queue
var/starttime = world.time
var/starttimeofday = world.timeofday
var/idex = 1
#ifdef REFERENCE_TRACKING
var/ref_searching = FALSE
#endif
while((queue.len - (idex - 1)) && starttime == world.time && starttimeofday == world.timeofday)
if (MC_TICK_CHECK)
break
#ifdef REFERENCE_TRACKING
if (ref_searching)
break
#endif
var/refID = queue[idex]
if (!refID)
idex++
/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
/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
var/list/queue = queues[level]
var/static/lastlevel
var/static/count = 0
if (count) //runtime last run before we could do this.
var/c = count
count = 0 //so if we runtime on the Cut, we don't try again.
var/list/lastqueue = queues[lastlevel]
lastqueue.Cut(1, c+1)
lastlevel = level
//We do this rather then for(var/list/ref_info 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/L = queue[i]
if (length(L) < GC_QUEUE_ITEM_INDEX_COUNT)
count++
if (MC_TICK_CHECK)
return
continue
var/GCd_at_time = queue[refID]
if(GCd_at_time > time_to_kill)
var/queued_at_time = L[GC_QUEUE_ITEM_QUEUE_TIME]
if(queued_at_time > cut_off_time)
break // Everything else is newer, skip them
idex++
var/datum/A
A = locate(refID)
if (A && A.gcDestroyed == GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake
#ifdef REFERENCE_TRACKING
if(reference_find_on_fail[text_ref(A)])
INVOKE_ASYNC(A, TYPE_PROC_REF(/datum, find_references))
ref_searching = TRUE
#ifdef GC_FAILURE_HARD_LOOKUP
else
INVOKE_ASYNC(A, TYPE_PROC_REF(/datum, find_references))
ref_searching = TRUE
#endif
reference_find_on_fail -= text_ref(A)
#endif
count++
// Something's still referring to the qdel'd object. Kill it.
var/type = A.type
log_subsystem_garbage_harddel("-- \ref[A] | [type] was unable to be GC'd and was deleted --", type)
didntgc["[type]"]++
var/datum/D = L[GC_QUEUE_ITEM_REF]
#ifdef REFERENCE_TRACKING
if(ref_searching)
continue //ref searching intentionally cancels all further fires while running so things that hold references don't end up getting deleted, so we want to return here instead of continue
#endif
HardDelete(A)
++delslasttick
++totaldels
else
// 1 from the hard reference in the queue, and 1 from the variable used before this
// If that's all we've got, send er off
if (refcount(D) == 2)
++gcedlasttick
++totalgcs
pass_counts[level]++
#ifdef REFERENCE_TRACKING
reference_find_on_fail -= text_ref(A)
reference_find_on_fail -= text_ref(D) //It's deleted we don't care anymore.
#endif
if (MC_TICK_CHECK)
return
continue
if (idex > 1)
queue.Cut(1, idex)
// Something's still referring to the qdel'd object.
fail_counts[level]++
/datum/controller/subsystem/garbage/proc/QueueForQueuing(datum/A)
if (istype(A) && A.gcDestroyed == GC_CURRENTLY_BEING_QDELETED)
tobequeued += A
A.gcDestroyed = GC_QUEUED_FOR_QUEUING
#ifdef REFERENCE_TRACKING
var/ref_searching = FALSE
#endif
/datum/controller/subsystem/garbage/proc/Queue(datum/A)
if (!istype(A) || (!isnull(A.gcDestroyed) && A.gcDestroyed >= 0))
switch (level)
if (GC_QUEUE_CHECK)
#ifdef REFERENCE_TRACKING
if(reference_find_on_fail[text_ref(D)])
INVOKE_ASYNC(D, TYPE_PROC_REF(/datum,find_references))
ref_searching = TRUE
#ifdef GC_FAILURE_HARD_LOOKUP
else
INVOKE_ASYNC(D, TYPE_PROC_REF(/datum,find_references))
ref_searching = TRUE
#endif
reference_find_on_fail -= text_ref(D)
#endif
var/type = D.type
var/datum/qdel_item/I = items[type]
var/message = "## TESTING: GC: -- [text_ref(D)] | [type] was unable to be GC'd --"
message = "[message] (ref count of [refcount(D)])"
log_world(message)
var/detail = D.dump_harddel_info()
if(detail)
LAZYADD(I.extra_details, detail)
#ifdef TESTING
for(var/c in GLOB.staff) //Using testing() here would fill the logs with ADMIN_VV garbage
var/client/admin = c
if(!check_rights(R_ADMIN, user = admin?.mob))
continue
// Used to be to_chat(admin, "## TESTING: GC: -- [ADMIN_VV(D)] | [type] was unable to be GC'd --") but we do not have that macro
to_chat(admin, "## TESTING: GC: -- <a href='?_src_=vars;Vars=\ref[D]'>VV</a> | [type] was unable to be GC'd --")
#endif
I.failures++
if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG)
#ifdef REFERENCE_TRACKING
if(ref_searching)
return //ref searching intentionally cancels all further fires while running so things that hold references don't end up getting deleted, so we want to return here instead of continue
#endif
continue
if (GC_QUEUE_HARDDELETE)
HardDelete(D)
if (MC_TICK_CHECK)
return
continue
Queue(D, level+1)
#ifdef REFERENCE_TRACKING
if(ref_searching)
return
#endif
if (MC_TICK_CHECK)
return
if (count)
queue.Cut(1,count+1)
count = 0
/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_FILTER)
if (isnull(D))
return
if (A.gcDestroyed == GC_QUEUED_FOR_HARD_DEL)
HardDelete(A)
if (level > GC_QUEUE_COUNT)
HardDelete(D)
return
var/gctime = world.time
var/refid = "\ref[A]"
var/queue_time = world.time
A.gcDestroyed = gctime
if (D.gc_destroyed <= 0)
D.gc_destroyed = queue_time
if (queue[refid])
queue -= refid // Removing any previous references that were GC'd so that the current object will be at the end of the list.
var/list/queue = queues[level]
queue[++queue.len] = list(queue_time, D, D.gc_destroyed) // not += for byond reasons
queue[refid] = gctime
//this is mainly to separate things profile wise.
/datum/controller/subsystem/garbage/proc/HardDelete(datum/D)
++delslasttick
++totaldels
var/type = D.type
var/refID = text_ref(D)
var/datum/qdel_item/type_info = items[type]
var/detail = D.dump_harddel_info()
if(detail)
LAZYADD(type_info.extra_details, detail)
// For profiling.
/datum/controller/subsystem/garbage/proc/HardDelete(datum/A)
var/time = world.timeofday
var/tick = world.tick_usage
var/ticktime = world.time
var/tick_usage = TICK_USAGE
del(D)
tick_usage = TICK_USAGE_TO_MS(tick_usage)
var/type = A.type
var/refID = "\ref[A]"
type_info.hard_deletes++
type_info.hard_delete_time += tick_usage
if (tick_usage > type_info.hard_delete_max)
type_info.hard_delete_max = tick_usage
if (tick_usage > highest_del_ms)
highest_del_ms = tick_usage
highest_del_type_string = "[type]"
del(A)
var/time = MS2DS(tick_usage)
tick = (world.tick_usage-tick+((world.time-ticktime)/world.tick_lag*100))
if (tick > highest_del_tickusage)
highest_del_tickusage = tick
time = world.timeofday - time
if (!time && TICK_DELTA_TO_MS(tick) > 1)
time = TICK_DELTA_TO_MS(tick)/100
if (time > highest_del_time)
highest_del_time = time
if (time > 10)
log_subsystem_garbage_error("Error: [type]([refID]) took longer then 1 second to delete (took [time/10] seconds to delete)", type, TRUE)
message_admins("Error: [type]([refID]) took longer then 1 second to delete (took [time/10] seconds to delete).")
postpone(time/5)
/datum/controller/subsystem/garbage/proc/HardQueue(datum/A)
if (istype(A) && A.gcDestroyed == GC_CURRENTLY_BEING_QDELETED)
tobequeued += A
A.gcDestroyed = GC_QUEUED_FOR_HARD_DEL
if (time > 0.1 SECONDS)
postpone(time)
var/threshold = 0.5 // Used to be CONFIG_GET(number/hard_deletes_overrun_threshold)
if (threshold && (time > threshold SECONDS))
if (!(type_info.qdel_flags & QDEL_ITEM_ADMINS_WARNED))
log_game("Error: [type]([refID]) took longer than [threshold] seconds to delete (took [round(time/10, 0.1)] seconds to delete)")
message_admins("Error: [type]([refID]) took longer than [threshold] seconds to delete (took [round(time/10, 0.1)] seconds to delete).")
type_info.qdel_flags |= QDEL_ITEM_ADMINS_WARNED
type_info.hard_deletes_over_threshold++
var/overrun_limit = 0 // Used to be CONFIG_GET(number/hard_deletes_overrun_limit)
if (overrun_limit && type_info.hard_deletes_over_threshold >= overrun_limit)
type_info.qdel_flags |= QDEL_ITEM_SUSPENDED_FOR_LAG
/datum/controller/subsystem/garbage/Recover()
if (istype(SSgarbage.queue))
queue |= SSgarbage.queue
if (istype(SSgarbage.tobequeued))
tobequeued |= SSgarbage.tobequeued
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]
// Should be treated as a replacement for the 'del' keyword.
// Datums passed to this will be given a chance to clean up references to allow the GC to collect them.
/proc/qdel(datum/D, force=FALSE)
if(!D)
/// Qdel Item: Holds statistics on each type that passes thru qdel
/datum/qdel_item
var/name = "" //!Holds the type as a string for this type
var/qdels = 0 //!Total number of times it's passed thru qdel.
var/destroy_time = 0 //!Total amount of milliseconds spent processing this type's Destroy()
var/failures = 0 //!Times it was queued for soft deletion but failed to soft delete.
var/hard_deletes = 0 //!Different from failures because it also includes QDEL_HINT_HARDDEL deletions
var/hard_delete_time = 0 //!Total amount of milliseconds spent hard deleting this type.
var/hard_delete_max = 0 //!Highest time spent hard_deleting this in ms.
var/hard_deletes_over_threshold = 0 //!Number of times hard deletes took longer than the configured threshold
var/no_respect_force = 0 //!Number of times it's not respected force=TRUE
var/no_hint = 0 //!Number of times it's not even bother to give a qdel hint
var/slept_destroy = 0 //!Number of times it's slept in its destroy
var/qdel_flags = 0 //!Flags related to this type's trip thru qdel.
var/list/extra_details //!Lazylist of string metadata about the deleted objects
/datum/qdel_item/New(mytype)
name = "[mytype]"
/// Should be treated as a replacement for the 'del' keyword.
///
/// Datums passed to this will be given a chance to clean up references to allow the GC to collect them.
/proc/qdel(datum/to_delete, force = FALSE)
if(!istype(to_delete))
del(to_delete)
return
#ifdef REFERENCE_TRACKING
SSgarbage.qdel_list += "[D.type]"
#endif
if(!istype(D))
del(D)
else if(isnull(D.gcDestroyed))
if (SEND_SIGNAL(D, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
var/datum/qdel_item/trash = SSgarbage.items[to_delete.type]
if (isnull(trash))
trash = SSgarbage.items[to_delete.type] = new /datum/qdel_item(to_delete.type)
trash.qdels++
if(!isnull(to_delete.gc_destroyed))
if(to_delete.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
CRASH("[to_delete.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
return
if (SEND_SIGNAL(to_delete, COMSIG_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
return
to_delete.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
var/start_time = world.time
var/start_tick = world.tick_usage
SEND_SIGNAL(to_delete, COMSIG_QDELETING, force) // Let the (remaining) components know about the result of Destroy
var/hint = to_delete.Destroy(force) // Let our friend know they're about to get fucked up.
if(world.time != start_time)
trash.slept_destroy++
else
trash.destroy_time += TICK_USAGE_TO_MS(start_tick)
if(isnull(to_delete))
return
switch(hint)
if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
SSgarbage.Queue(to_delete)
if (QDEL_HINT_IWILLGC)
to_delete.gc_destroyed = world.time
return
// this SEND_SIGNAL should be above Destroy because Destroy sets signal_enabled to FALSE
SEND_SIGNAL(D, COMSIG_QDELETING, force) // Let the (remaining) components know about the result of Destroy
D.gcDestroyed = GC_CURRENTLY_BEING_QDELETED
var/start_time = world.time
var/hint = D.Destroy(force) // Let our friend know they're about to get fucked up.
if(world.time != start_time)
SSgarbage.sleptDestroy["[D.type]"]++
if(!D)
return
switch(hint)
if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
SSgarbage.QueueForQueuing(D)
if (QDEL_HINT_IWILLGC)
D.gcDestroyed = world.time
if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
if(!force)
to_delete.gc_destroyed = null //clear the gc variable (important!)
return
if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
if(!force)
D.gcDestroyed = null //clear the gc variable (important!)
return
// Returning LETMELIVE after being told to force destroy
// indicates the objects Destroy() does not respect force
if(!SSgarbage.noforcerespect["[D.type]"])
SSgarbage.noforcerespect["[D.type]"] = "[D.type]"
log_subsystem_garbage_warning("WARNING: [D.type] has been force deleted, but is \
returning an immortal QDEL_HINT, indicating it does \
not respect the force flag for qdel(). It has been \
placed in the queue, further instances of this type \
will also be queued.")
SSgarbage.QueueForQueuing(D)
if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete using a hard reference to save time from the locate()
SSgarbage.HardQueue(D)
if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
SSgarbage.HardDelete(D)
if (QDEL_HINT_FINDREFERENCE)//qdel will, if REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
SSgarbage.QueueForQueuing(D)
#ifdef REFERENCE_TRACKING
INVOKE_ASYNC(D, TYPE_PROC_REF(/datum, find_references), TRUE)
#endif
if (QDEL_HINT_IFFAIL_FINDREFERENCE) // qdel will, if REFERENCE_TRACKING is enabled and the object fails to collect, display all references to this object
SSgarbage.QueueForQueuing(D)
#ifdef REFERENCE_TRACKING
SSgarbage.reference_find_on_fail[text_ref(D)] = TRUE
#endif
else
if(!SSgarbage.noqdelhint["[D.type]"])
SSgarbage.noqdelhint["[D.type]"] = "[D.type]"
log_subsystem_garbage_warning("WARNING: [D.type] is not returning a qdel hint. It is being placed in the queue. Further instances of this type will also be queued.")
SSgarbage.QueueForQueuing(D)
else if(D.gcDestroyed == GC_CURRENTLY_BEING_QDELETED)
CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
/client/Destroy()
..()
return QDEL_HINT_HARDDEL_NOW
// REFERENCE TRACKING (the old garbage-debug file) //
// Only present if the appropriate define is set //
#ifdef REFERENCE_TRACKING
/datum/verb/find_refs()
set category = "Debug"
set name = "Find References"
set background = 1
set src in world
find_references(FALSE)
/client/verb/show_qdeleted()
set category = "Debug"
set name = "Show qdel() Log"
set desc = "Render the qdel() log and display it"
var/dat = "<B>List of things that have been qdel()eted this round</B><BR><BR>"
var/tmplist = list()
for(var/elem in SSgarbage.qdel_list)
if(!(elem in tmplist))
tmplist[elem] = 0
tmplist[elem]++
sortTim(tmplist, GLOBAL_PROC_REF(cmp_numeric_dsc), TRUE)
for(var/path in tmplist)
dat += "[path] - [tmplist[path]] times<BR>"
usr << browse(dat, "window=qdeletedlog")
/datum/proc/find_references(skip_alert)
running_find_references = type
if(usr?.client)
if(usr.client.running_find_references)
testing("CANCELLED search for references to a [usr.client.running_find_references].")
usr.client.running_find_references = null
running_find_references = null
SSgarbage.can_fire = TRUE
SSgarbage.update_nextfire(reset_time = TRUE)
return
if(!skip_alert && alert(usr, "Running this will lock everything up for 5+ minutes. Would you like to begin the search?", "Find References", "Yes", "No") != "Yes")
running_find_references = null
return
SSgarbage.can_fire = FALSE // Keeps the GC from failing to collect objects being searched for here
if(usr?.client)
usr.client.running_find_references = type
//Time to search the whole game for our ref
testing("Beginning search for references to a [type].")
var/starting_time = world.time
//Yes we do actually need to do this. The searcher refuses to read weird lists
//And global.vars is a really weird list
var/global_vars = list()
for(var/key in global.vars)
global_vars[key] = global.vars[key]
search_var(global_vars, "Native Global", search_time = starting_time)
testing("Finished searching native globals")
for(var/datum/thing in world) // atoms (don't believe its lies)
search_var(thing, "World -> [thing.type]", search_time = starting_time)
testing("Finished searching atoms")
for(var/datum/thing) // datums
search_var(thing, "Datums -> [thing.type]", search_time = starting_time)
testing("Finished searching datums")
//Warning, attempting to search clients like this will cause crashes if done on live. Watch yourself
for(var/client/thing) // clients
search_var(thing, "Clients -> [thing.type]", search_time = starting_time)
testing("Finished searching clients")
testing("Completed all searches for references to a [type].")
if(usr?.client)
usr.client.running_find_references = null
running_find_references = null
SSgarbage.can_fire = TRUE //restart the garbage collector
SSgarbage.update_nextfire(reset_time = TRUE) //restart the garbage collector
/datum/proc/search_var(potential_container, container_name, recursive_limit = 64, search_time = world.time)
//If we are performing a search without a check tick, we should avoid sleeping
#if defined(FIND_REF_NO_CHECK_TICK)
SHOULD_NOT_SLEEP(TRUE)
#endif
#ifdef REFERENCE_TRACKING_DEBUG
if(SSgarbage.should_save_refs && !found_refs)
found_refs = list()
#endif
if(usr?.client && !usr.client.running_find_references)
return
if(!recursive_limit)
testing("Recursion limit reached. [container_name]")
return
//Check each time you go down a layer. This makes it a bit slow, but it won't effect the rest of the game at all
#ifndef FIND_REF_NO_CHECK_TICK
CHECK_TICK
#endif
if(isdatum(potential_container))
var/datum/datum_container = potential_container
if(datum_container.last_find_references == search_time)
return
datum_container.last_find_references = search_time
var/list/vars_list = datum_container.vars
for(var/varname in vars_list)
#ifndef FIND_REF_NO_CHECK_TICK
CHECK_TICK
// Returning LETMELIVE after being told to force destroy
// indicates the objects Destroy() does not respect force
#ifdef TESTING
if(!trash.no_respect_force)
testing("WARNING: [to_delete.type] has been force deleted, but is \
returning an immortal QDEL_HINT, indicating it does \
not respect the force flag for qdel(). It has been \
placed in the queue, further instances of this type \
will also be queued.")
#endif
if (varname == "vars" || varname == "vis_locs") //Fun fact, vis_locs don't count for references
continue
var/variable = vars_list[varname]
trash.no_respect_force++
if(variable == src)
#ifdef REFERENCE_TRACKING_DEBUG
if(SSgarbage.should_save_refs)
found_refs[varname] = TRUE
continue //End early, don't want these logging
#endif
testing("Found [type] [text_ref(src)] in [datum_container.type]'s [text_ref(datum_container)] [varname] var. [container_name]")
continue
if(islist(variable))
search_var(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)
var/list/potential_cache = potential_container
for(var/element_in_list in potential_cache)
#ifndef FIND_REF_NO_CHECK_TICK
CHECK_TICK
SSgarbage.Queue(to_delete)
if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete
SSgarbage.Queue(to_delete, GC_QUEUE_HARDDELETE)
if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
SSgarbage.HardDelete(to_delete)
#ifdef REFERENCE_TRACKING
if (QDEL_HINT_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
SSgarbage.Queue(to_delete)
INVOKE_ASYNC(to_delete, TYPE_PROC_REF(/datum, find_references))
if (QDEL_HINT_IFFAIL_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled and the object fails to collect, display all references to this object.
SSgarbage.Queue(to_delete)
SSgarbage.reference_find_on_fail[text_ref(to_delete)] = TRUE
#endif
else
#ifdef TESTING
if(!trash.no_hint)
testing("WARNING: [to_delete.type] is not returning a qdel hint. It is being placed in the queue. Further instances of this type will also be queued.")
#endif
// Check normal entries
if(element_in_list == src)
#ifdef REFERENCE_TRACKING_DEBUG
if(SSgarbage.should_save_refs)
found_refs[potential_cache] = TRUE
continue //End early, don't want these logging
#endif
testing("Found [type] [text_ref(src)] in list [container_name]\[[element_in_list]\]")
continue
var/assoc_val = null
if(!isnum(element_in_list) && normal)
assoc_val = potential_cache[element_in_list]
// Check assoc entries
if(assoc_val == src)
#ifdef REFERENCE_TRACKING_DEBUG
if(SSgarbage.should_save_refs)
found_refs[potential_cache] = TRUE
continue //End early, don't want these logging
#endif
testing("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
if(islist(element_in_list))
search_var(element_in_list, "[container_name] -> [element_in_list] (list)", recursive_limit - 1, search_time)
// Check assoc sublists
if(islist(assoc_val))
search_var(potential_container[element_in_list], "[container_name]\[[element_in_list]\] -> [assoc_val] (list)", recursive_limit - 1, search_time)
/proc/qdel_and_find_ref_if_fail(datum/thing_to_qdel, force = FALSE)
thing_to_qdel.qdel_and_find_ref_if_fail(force)
/datum/proc/qdel_and_find_ref_if_fail(force = FALSE)
SSgarbage.reference_find_on_fail[text_ref(src)] = TRUE
qdel(src, force)
#endif
trash.no_hint++
SSgarbage.Queue(to_delete)
+12
View File
@@ -108,6 +108,8 @@ SUBSYSTEM_DEF(radio)
return ..()
/datum/controller/subsystem/radio/proc/add_object(obj/device, new_frequency, filter = null)
SHOULD_NOT_SLEEP(TRUE)
var/f_text = num2text(new_frequency)
var/datum/radio_frequency/frequency = frequencies[f_text]
@@ -120,6 +122,8 @@ SUBSYSTEM_DEF(radio)
return frequency
/datum/controller/subsystem/radio/proc/remove_object(obj/device, old_frequency)
SHOULD_NOT_SLEEP(TRUE)
var/f_text = num2text(old_frequency)
var/datum/radio_frequency/frequency = frequencies[f_text]
@@ -127,10 +131,14 @@ SUBSYSTEM_DEF(radio)
frequency.remove_listener(device)
/datum/controller/subsystem/radio/proc/remove_object_all(obj/device)
SHOULD_NOT_SLEEP(TRUE)
for(var/freq in frequencies)
SSradio.remove_object(device, text2num(freq))
/datum/controller/subsystem/radio/proc/get_devices(freq, filter = RADIO_DEFAULT)
SHOULD_NOT_SLEEP(TRUE)
var/datum/radio_frequency/frequency = frequencies[num2text(freq)]
if(!frequency)
return
@@ -150,6 +158,8 @@ SUBSYSTEM_DEF(radio)
// Used to test connectivity to the telecomms network.
/datum/controller/subsystem/radio/proc/telecomms_ping(obj/O, test_freq = PUB_FREQ)
SHOULD_NOT_SLEEP(TRUE)
var/datum/signal/subspace/testsig = new(O, test_freq)
for (var/obj/machinery/telecomms/R in SSmachinery.all_receivers)
if(R.receive_range(testsig) >= 0)
@@ -159,6 +169,8 @@ SUBSYSTEM_DEF(radio)
//callback used by objects to react to incoming radio signals
/obj/proc/receive_signal(datum/signal/signal, receive_method, receive_param)
SHOULD_NOT_SLEEP(TRUE)
return null
/proc/frequency_span_class(var/frequency)
@@ -131,7 +131,7 @@ SUBSYSTEM_DEF(spatial_grid)
if(movable_turf)
enter_cell(movable, movable_turf)
UnregisterSignal(movable, COMSIG_PARENT_PREQDELETED)
UnregisterSignal(movable, COMSIG_PREQDELETED)
waiting_to_add_by_type[channel_type] -= movable
pregenerate_more_oranges_ears(NUMBER_OF_PREGENERATED_ORANGES_EARS)
@@ -143,7 +143,7 @@ SUBSYSTEM_DEF(spatial_grid)
///add a movable to the pre init queue for whichever type is specified so that when the subsystem initializes they get added to the grid
/datum/controller/subsystem/spatial_grid/proc/enter_pre_init_queue(atom/movable/waiting_movable, type)
RegisterSignal(waiting_movable, COMSIG_PARENT_PREQDELETED, PROC_REF(queued_item_deleted), override = TRUE)
RegisterSignal(waiting_movable, COMSIG_PREQDELETED, PROC_REF(queued_item_deleted), override = TRUE)
//override because something can enter the queue for two different types but that is done through unrelated procs that shouldnt know about eachother
waiting_to_add_by_type[type] += waiting_movable
@@ -158,11 +158,11 @@ SUBSYSTEM_DEF(spatial_grid)
waiting_movable_is_in_other_queues = TRUE
if(!waiting_movable_is_in_other_queues)
UnregisterSignal(movable_to_remove, COMSIG_PARENT_PREQDELETED)
UnregisterSignal(movable_to_remove, COMSIG_PREQDELETED)
return
UnregisterSignal(movable_to_remove, COMSIG_PARENT_PREQDELETED)
UnregisterSignal(movable_to_remove, COMSIG_PREQDELETED)
for(var/type in waiting_to_add_by_type)
waiting_to_add_by_type[type] -= movable_to_remove