You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

441 lines
13 KiB

  1. SUBSYSTEM_DEF(garbage)
  2. name = "Garbage"
  3. priority = FIRE_PRIORITY_GARBAGE
  4. wait = 2 SECONDS
  5. flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT
  6. runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
  7. init_order = INIT_ORDER_GARBAGE
  8. var/list/collection_timeout = list(0, 2 MINUTES, 10 SECONDS) // deciseconds to wait before moving something up in the queue to the next level
  9. //Stat tracking
  10. var/delslasttick = 0 // number of del()'s we've done this tick
  11. var/gcedlasttick = 0 // number of things that gc'ed last tick
  12. var/totaldels = 0
  13. var/totalgcs = 0
  14. var/highest_del_time = 0
  15. var/highest_del_tickusage = 0
  16. var/list/pass_counts
  17. var/list/fail_counts
  18. var/list/items = list() // Holds our qdel_item statistics datums
  19. //Queue
  20. var/list/queues
  21. #ifdef TESTING
  22. var/list/reference_find_on_fail = list()
  23. #endif
  24. /datum/controller/subsystem/garbage/PreInit()
  25. queues = new(GC_QUEUE_COUNT)
  26. pass_counts = new(GC_QUEUE_COUNT)
  27. fail_counts = new(GC_QUEUE_COUNT)
  28. for(var/i in 1 to GC_QUEUE_COUNT)
  29. queues[i] = list()
  30. pass_counts[i] = 0
  31. fail_counts[i] = 0
  32. /datum/controller/subsystem/garbage/stat_entry(msg)
  33. var/list/counts = list()
  34. for (var/list/L in queues)
  35. counts += length(L)
  36. msg += "Q:[counts.Join(",")]|D:[delslasttick]|G:[gcedlasttick]|"
  37. msg += "GR:"
  38. if (!(delslasttick+gcedlasttick))
  39. msg += "n/a|"
  40. else
  41. msg += "[round((gcedlasttick/(delslasttick+gcedlasttick))*100, 0.01)]%|"
  42. msg += "TD:[totaldels]|TG:[totalgcs]|"
  43. if (!(totaldels+totalgcs))
  44. msg += "n/a|"
  45. else
  46. msg += "TGR:[round((totalgcs/(totaldels+totalgcs))*100, 0.01)]%"
  47. msg += " P:[pass_counts.Join(",")]"
  48. msg += "|F:[fail_counts.Join(",")]"
  49. ..(msg)
  50. /datum/controller/subsystem/garbage/Shutdown()
  51. //Adds the del() log to the qdel log file
  52. var/list/dellog = list()
  53. //sort by how long it's wasted hard deleting
  54. sortTim(items, cmp=/proc/cmp_qdel_item_time, associative = TRUE)
  55. for(var/path in items)
  56. var/datum/qdel_item/I = items[path]
  57. dellog += "Path: [path]"
  58. if (I.failures)
  59. dellog += "\tFailures: [I.failures]"
  60. dellog += "\tqdel() Count: [I.qdels]"
  61. dellog += "\tDestroy() Cost: [I.destroy_time]ms"
  62. if (I.hard_deletes)
  63. dellog += "\tTotal Hard Deletes [I.hard_deletes]"
  64. dellog += "\tTime Spent Hard Deleting: [I.hard_delete_time]ms"
  65. if (I.slept_destroy)
  66. dellog += "\tSleeps: [I.slept_destroy]"
  67. if (I.no_respect_force)
  68. dellog += "\tIgnored force: [I.no_respect_force] times"
  69. if (I.no_hint)
  70. dellog += "\tNo hint: [I.no_hint] times"
  71. log_qdel(dellog.Join("\n"))
  72. /datum/controller/subsystem/garbage/fire()
  73. //the fact that this resets its processing each fire (rather then resume where it left off) is intentional.
  74. var/queue = GC_QUEUE_CHECK
  75. while (state == SS_RUNNING)
  76. switch (queue)
  77. if (GC_QUEUE_CHECK)
  78. HandleQueue(GC_QUEUE_CHECK)
  79. queue = GC_QUEUE_CHECK+1
  80. if (GC_QUEUE_HARDDELETE)
  81. HandleQueue(GC_QUEUE_HARDDELETE)
  82. if (state == SS_PAUSED) //make us wait again before the next run.
  83. state = SS_RUNNING
  84. break
  85. /datum/controller/subsystem/garbage/proc/HandleQueue(level = GC_QUEUE_CHECK)
  86. if (level == GC_QUEUE_CHECK)
  87. delslasttick = 0
  88. gcedlasttick = 0
  89. var/cut_off_time = world.time - collection_timeout[level] //ignore entries newer then this
  90. var/list/queue = queues[level]
  91. var/static/lastlevel
  92. var/static/count = 0
  93. if (count) //runtime last run before we could do this.
  94. var/c = count
  95. count = 0 //so if we runtime on the Cut, we don't try again.
  96. var/list/lastqueue = queues[lastlevel]
  97. lastqueue.Cut(1, c+1)
  98. lastlevel = level
  99. for (var/refID in queue)
  100. if (!refID)
  101. count++
  102. if (MC_TICK_CHECK)
  103. return
  104. continue
  105. var/GCd_at_time = queue[refID]
  106. if(GCd_at_time > cut_off_time)
  107. break // Everything else is newer, skip them
  108. count++
  109. var/datum/D
  110. D = locate(refID)
  111. if (!D || D.gc_destroyed != GCd_at_time) // So if something else coincidently gets the same ref, it's not deleted by mistake
  112. ++gcedlasttick
  113. ++totalgcs
  114. pass_counts[level]++
  115. #ifdef TESTING
  116. reference_find_on_fail -= refID //It's deleted we don't care anymore.
  117. #endif
  118. if (MC_TICK_CHECK)
  119. return
  120. continue
  121. // Something's still referring to the qdel'd object.
  122. fail_counts[level]++
  123. switch (level)
  124. if (GC_QUEUE_CHECK)
  125. #ifdef TESTING
  126. if(reference_find_on_fail[refID])
  127. D.find_references()
  128. #ifdef GC_FAILURE_HARD_LOOKUP
  129. else
  130. D.find_references()
  131. #endif
  132. reference_find_on_fail -= refID
  133. #endif
  134. var/type = D.type
  135. var/datum/qdel_item/I = items[type]
  136. testing("GC: -- \ref[src] | [type] was unable to be GC'd --")
  137. I.failures++
  138. if (GC_QUEUE_HARDDELETE)
  139. HardDelete(D)
  140. if (MC_TICK_CHECK)
  141. return
  142. continue
  143. Queue(D, level+1)
  144. if (MC_TICK_CHECK)
  145. return
  146. if (count)
  147. queue.Cut(1,count+1)
  148. count = 0
  149. /datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_CHECK)
  150. if (isnull(D))
  151. return
  152. if (level > GC_QUEUE_COUNT)
  153. HardDelete(D)
  154. return
  155. var/gctime = world.time
  156. var/refid = "\ref[D]"
  157. D.gc_destroyed = gctime
  158. var/list/queue = queues[level]
  159. if (queue[refid])
  160. queue -= refid // Removing any previous references that were GC'd so that the current object will be at the end of the list.
  161. queue[refid] = gctime
  162. //this is mainly to separate things profile wise.
  163. /datum/controller/subsystem/garbage/proc/HardDelete(datum/D)
  164. var/time = world.timeofday
  165. var/tick = TICK_USAGE
  166. var/ticktime = world.time
  167. ++delslasttick
  168. ++totaldels
  169. var/type = D.type
  170. var/refID = "\ref[D]"
  171. del(D)
  172. tick = (TICK_USAGE-tick+((world.time-ticktime)/world.tick_lag*100))
  173. var/datum/qdel_item/I = items[type]
  174. I.hard_deletes++
  175. I.hard_delete_time += TICK_DELTA_TO_MS(tick)
  176. if (tick > highest_del_tickusage)
  177. highest_del_tickusage = tick
  178. time = world.timeofday - time
  179. if (!time && TICK_DELTA_TO_MS(tick) > 1)
  180. time = TICK_DELTA_TO_MS(tick)/100
  181. if (time > highest_del_time)
  182. highest_del_time = time
  183. if (time > 10)
  184. log_game("Error: [type]([refID]) took longer than 1 second to delete (took [time/10] seconds to delete)")
  185. message_admins("Error: [type]([refID]) took longer than 1 second to delete (took [time/10] seconds to delete).")
  186. postpone(time)
  187. /datum/controller/subsystem/garbage/Recover()
  188. if (istype(SSgarbage.queues))
  189. for (var/i in 1 to SSgarbage.queues.len)
  190. queues[i] |= SSgarbage.queues[i]
  191. /datum/qdel_item
  192. var/name = ""
  193. var/qdels = 0 //Total number of times it's passed thru qdel.
  194. var/destroy_time = 0 //Total amount of milliseconds spent processing this type's Destroy()
  195. var/failures = 0 //Times it was queued for soft deletion but failed to soft delete.
  196. var/hard_deletes = 0 //Different from failures because it also includes QDEL_HINT_HARDDEL deletions
  197. var/hard_delete_time = 0//Total amount of milliseconds spent hard deleting this type.
  198. var/no_respect_force = 0//Number of times it's not respected force=TRUE
  199. var/no_hint = 0 //Number of times it's not even bother to give a qdel hint
  200. var/slept_destroy = 0 //Number of times it's slept in its destroy
  201. /datum/qdel_item/New(mytype)
  202. name = "[mytype]"
  203. #ifdef TESTING
  204. /proc/qdel_and_find_ref_if_fail(datum/D, force = FALSE)
  205. SSgarbage.reference_find_on_fail[REF(D)] = TRUE
  206. qdel(D, force)
  207. #endif
  208. // Should be treated as a replacement for the 'del' keyword.
  209. // Datums passed to this will be given a chance to clean up references to allow the GC to collect them.
  210. /proc/qdel(datum/D, force=FALSE, ...)
  211. if(!istype(D))
  212. del(D)
  213. return
  214. var/datum/qdel_item/I = SSgarbage.items[D.type]
  215. if (!I)
  216. I = SSgarbage.items[D.type] = new /datum/qdel_item(D.type)
  217. I.qdels++
  218. if(isnull(D.gc_destroyed))
  219. if (SEND_SIGNAL(D, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
  220. return
  221. D.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
  222. var/start_time = world.time
  223. var/start_tick = world.tick_usage
  224. SEND_SIGNAL(D, COMSIG_PARENT_QDELETING, force) // Let the (remaining) components know about the result of Destroy
  225. var/hint = D.Destroy(arglist(args.Copy(2))) // Let our friend know they're about to get fucked up.
  226. if(world.time != start_time)
  227. I.slept_destroy++
  228. else
  229. I.destroy_time += TICK_USAGE_TO_MS(start_tick)
  230. if(!D)
  231. return
  232. switch(hint)
  233. if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion.
  234. SSgarbage.Queue(D)
  235. if (QDEL_HINT_IWILLGC)
  236. D.gc_destroyed = world.time
  237. SSdemo.mark_destroyed(D)
  238. return
  239. if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory.
  240. if(!force)
  241. D.gc_destroyed = null //clear the gc variable (important!)
  242. return
  243. // Returning LETMELIVE after being told to force destroy
  244. // indicates the objects Destroy() does not respect force
  245. #ifdef TESTING
  246. if(!I.no_respect_force)
  247. testing("WARNING: [D.type] has been force deleted, but is \
  248. returning an immortal QDEL_HINT, indicating it does \
  249. not respect the force flag for qdel(). It has been \
  250. placed in the queue, further instances of this type \
  251. will also be queued.")
  252. #endif
  253. I.no_respect_force++
  254. SSgarbage.Queue(D)
  255. if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete
  256. SSdemo.mark_destroyed(D)
  257. SSgarbage.Queue(D, GC_QUEUE_HARDDELETE)
  258. if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
  259. SSdemo.mark_destroyed(D)
  260. SSgarbage.HardDelete(D)
  261. if (QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion.
  262. SSgarbage.Queue(D)
  263. #ifdef TESTING
  264. D.find_references()
  265. #endif
  266. if (QDEL_HINT_IFFAIL_FINDREFERENCE)
  267. SSgarbage.Queue(D)
  268. #ifdef TESTING
  269. SSgarbage.reference_find_on_fail[REF(D)] = TRUE
  270. #endif
  271. else
  272. #ifdef TESTING
  273. if(!I.no_hint)
  274. testing("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.")
  275. #endif
  276. I.no_hint++
  277. SSgarbage.Queue(D)
  278. if(D)
  279. SSdemo.mark_destroyed(D)
  280. else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
  281. CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
  282. #ifdef TESTING
  283. /datum/verb/find_refs()
  284. set category = "Debug"
  285. set name = "Find References"
  286. set src in world
  287. find_references(FALSE)
  288. /datum/proc/find_references(skip_alert)
  289. running_find_references = type
  290. if(usr && usr.client)
  291. if(usr.client.running_find_references)
  292. testing("CANCELLED search for references to a [usr.client.running_find_references].")
  293. usr.client.running_find_references = null
  294. running_find_references = null
  295. //restart the garbage collector
  296. SSgarbage.can_fire = 1
  297. SSgarbage.next_fire = world.time + world.tick_lag
  298. return
  299. if(!skip_alert)
  300. if(alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "Yes", "No") == "No")
  301. running_find_references = null
  302. return
  303. //this keeps the garbage collector from failing to collect objects being searched for in here
  304. SSgarbage.can_fire = 0
  305. if(usr && usr.client)
  306. usr.client.running_find_references = type
  307. testing("Beginning search for references to a [type].")
  308. last_find_references = world.time
  309. DoSearchVar(GLOB) //globals
  310. for(var/datum/thing in world) //atoms (don't beleive it's lies)
  311. DoSearchVar(thing, "World -> [thing]")
  312. for (var/datum/thing) //datums
  313. DoSearchVar(thing, "World -> [thing]")
  314. for (var/client/thing) //clients
  315. DoSearchVar(thing, "World -> [thing]")
  316. testing("Completed search for references to a [type].")
  317. if(usr && usr.client)
  318. usr.client.running_find_references = null
  319. running_find_references = null
  320. //restart the garbage collector
  321. SSgarbage.can_fire = 1
  322. SSgarbage.next_fire = world.time + world.tick_lag
  323. /datum/verb/qdel_then_find_references()
  324. set category = "Debug"
  325. set name = "qdel() then Find References"
  326. set src in world
  327. qdel(src, TRUE) //Force.
  328. if(!running_find_references)
  329. find_references(TRUE)
  330. /datum/verb/qdel_then_if_fail_find_references()
  331. set category = "Debug"
  332. set name = "qdel() then Find References if GC failure"
  333. set src in world
  334. qdel_and_find_ref_if_fail(src, TRUE)
  335. /datum/proc/DoSearchVar(X, Xname, recursive_limit = 64)
  336. if(usr && usr.client && !usr.client.running_find_references)
  337. return
  338. if (!recursive_limit)
  339. return
  340. if(istype(X, /datum))
  341. var/datum/D = X
  342. if(D.last_find_references == last_find_references)
  343. return
  344. D.last_find_references = last_find_references
  345. var/list/L = D.vars
  346. for(var/varname in L)
  347. if (varname == "vars")
  348. continue
  349. var/variable = L[varname]
  350. if(variable == src)
  351. testing("Found [src.type] \ref[src] in [D.type]'s [varname] var. [Xname]")
  352. else if(islist(variable))
  353. DoSearchVar(variable, "[Xname] -> list", recursive_limit-1)
  354. else if(islist(X))
  355. var/normal = IS_NORMAL_LIST(X)
  356. for(var/I in X)
  357. if (I == src)
  358. testing("Found [src.type] \ref[src] in list [Xname].")
  359. else if (I && !isnum(I) && normal && X[I] == src)
  360. testing("Found [src.type] \ref[src] in list [Xname]\[[I]\]")
  361. else if (islist(I))
  362. DoSearchVar(I, "[Xname] -> list", recursive_limit-1)
  363. #ifndef FIND_REF_NO_CHECK_TICK
  364. CHECK_TICK
  365. #endif
  366. #endif