From 97db278cab330e04bd79eca8307cf42d258cb0b5 Mon Sep 17 00:00:00 2001 From: Fluffy <65877598+FluffyGhoster@users.noreply.github.com> Date: Mon, 6 Nov 2023 17:48:46 +0100 Subject: [PATCH] Reference tracking tweaks (#17725) * Atomization * sdfas * sdf * Update html/changelogs/FluffyGhost-reference_tracking_tweaks.yml Co-authored-by: SleepyGemmy <99297919+SleepyGemmy@users.noreply.github.com> --------- Co-authored-by: FluffyGhost Co-authored-by: SleepyGemmy <99297919+SleepyGemmy@users.noreply.github.com> --- aurorastation.dme | 1 - code/controllers/subsystems/garbage-debug.dm | 159 -------------- code/controllers/subsystems/garbage.dm | 194 +++++++++++++++++- code/datums/datum.dm | 26 ++- .../FluffyGhost-reference_tracking_tweaks.yml | 44 ++++ 5 files changed, 259 insertions(+), 165 deletions(-) delete mode 100644 code/controllers/subsystems/garbage-debug.dm create mode 100644 html/changelogs/FluffyGhost-reference_tracking_tweaks.yml diff --git a/aurorastation.dme b/aurorastation.dme index 8b4da64a464..43f17f77a0e 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -252,7 +252,6 @@ #include "code\controllers\subsystems\explosives.dm" #include "code\controllers\subsystems\fail2topic.dm" #include "code\controllers\subsystems\falling.dm" -#include "code\controllers\subsystems\garbage-debug.dm" #include "code\controllers\subsystems\garbage.dm" #include "code\controllers\subsystems\ghostroles.dm" #include "code\controllers\subsystems\global_listener.dm" diff --git a/code/controllers/subsystems/garbage-debug.dm b/code/controllers/subsystems/garbage-debug.dm deleted file mode 100644 index eb4b009d558..00000000000 --- a/code/controllers/subsystems/garbage-debug.dm +++ /dev/null @@ -1,159 +0,0 @@ -#ifdef REFERENCE_TRACKING - -/datum/var/running_find_references -/datum/var/last_find_references = 0 - -/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 = "List of things that have been qdel()eted this round

" - - 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
" - - 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.enable() - 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.disable() // 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.enable() //restart the garbage collector - -/datum/proc/search_var(potential_container, container_name, recursive_limit = 64, search_time = world.time) - - 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 - #endif - if (varname == "vars" || varname == "vis_locs") //Fun fact, vis_locs don't count for references - continue - var/variable = vars_list[varname] - - if(variable == src) - 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 - #endif - // Check normal entries - if(element_in_list == src) - 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) - 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 diff --git a/code/controllers/subsystems/garbage.dm b/code/controllers/subsystems/garbage.dm index d05c0e3525d..dd7830aaaf1 100644 --- a/code/controllers/subsystems/garbage.dm +++ b/code/controllers/subsystems/garbage.dm @@ -30,12 +30,16 @@ SUBSYSTEM_DEF(garbage) // of the immortality qdel hints var/list/noforcerespect = list() - #ifdef TESTING + #ifdef REFERENCE_TRACKING var/list/qdel_list = list() // list of all types that have been qdel()eted #endif #ifdef REFERENCE_TRACKING var/list/reference_find_on_fail = list() + #ifdef REFERENCE_TRACKING_DEBUG + //Should we save found refs. Used for unit testing + var/should_save_refs = FALSE + #endif #endif /datum/controller/subsystem/garbage/stat_entry(msg) @@ -205,7 +209,7 @@ SUBSYSTEM_DEF(garbage) /proc/qdel(datum/D, force=FALSE) if(!D) return -#ifdef TESTING +#ifdef REFERENCE_TRACKING SSgarbage.qdel_list += "[D.type]" #endif if(!istype(D)) @@ -248,7 +252,7 @@ SUBSYSTEM_DEF(garbage) 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 - D.find_references() + INVOKE_ASYNC(D, TYPE_PROC_REF(/datum, find_references)) #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) @@ -266,3 +270,187 @@ SUBSYSTEM_DEF(garbage) /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 = "List of things that have been qdel()eted this round

" + + 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
" + + 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.enable() + 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.disable() // 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.enable() //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 + #endif + if (varname == "vars" || varname == "vis_locs") //Fun fact, vis_locs don't count for references + continue + var/variable = vars_list[varname] + + 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 + #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 diff --git a/code/datums/datum.dm b/code/datums/datum.dm index 26ffa564a00..a66c0a3adcf 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -2,7 +2,14 @@ var/tmp/list/active_timers var/tmp/datum/weakref/weakref var/tmp/isprocessing = 0 - var/tmp/gcDestroyed //Time when this object was destroyed. + + /** + * Tick count time when this object was destroyed. + * + * If this is non zero then the object has been garbage collected and is awaiting either + * a hard del by the GC subsystme, or to be autocollected (if it has no references) + */ + var/tmp/gcDestroyed /// Status traits attached to this datum. associative list of the form: list(trait name (string) = list(source1, source2, source3,...)) var/list/status_traits @@ -22,11 +29,20 @@ /// This avoids doing that more then once per datum by ensuring ref strings always have a reference to them after they're first pulled var/cached_ref +#ifdef REFERENCE_TRACKING + var/running_find_references + var/last_find_references = 0 + #ifdef REFERENCE_TRACKING_DEBUG + ///Stores info about where refs are found, used for sanity checks and testing + var/list/found_refs + #endif +#endif + // Default implementation of clean-up code. // This should be overridden to remove all references pointing to the object being destroyed. // Return the appropriate QDEL_HINT; in most cases this is QDEL_HINT_QUEUE. /datum/proc/Destroy(force=FALSE) - SHOULD_CALL_PARENT(1) + SHOULD_CALL_PARENT(TRUE) weakref = null destroyed_event.raise_event(src) @@ -46,6 +62,12 @@ // Handle components & signals signal_enabled = FALSE + #ifdef REFERENCE_TRACKING + #ifdef REFERENCE_TRACKING_DEBUG + found_refs = null + #endif + #endif + var/list/dc = datum_components if(dc) var/all_components = dc[/datum/component] diff --git a/html/changelogs/FluffyGhost-reference_tracking_tweaks.yml b/html/changelogs/FluffyGhost-reference_tracking_tweaks.yml new file mode 100644 index 00000000000..35d77abf09b --- /dev/null +++ b/html/changelogs/FluffyGhost-reference_tracking_tweaks.yml @@ -0,0 +1,44 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +# balance +# admin +# backend +# security +# refactor +################################# + +# Your name. +author: FluffyGhost + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - refactor: "Brought garbage-debug.dm back into garbage." + - refactor: "Ported some parts of TG's reference tracking that were missing." + - bugfix: "Fixed an issue that allowed the show harddels verb to be defined but the backend it relies upon not to." + - backend: "DMdoced a variable."