From a435d73450f5bf1886a48cc67d0e638c1e1dca5a Mon Sep 17 00:00:00 2001 From: Leshana Date: Wed, 27 Dec 2017 19:54:45 -0500 Subject: [PATCH] Ports /tg controller optimizations https://github.com/tgstation/tgstation/pull/31092 - Fixes subsystems not returning a qdel hint https://github.com/tgstation/tgstation/pull/31494 - In which the stoner one gets stoned and tries to address tick contention... again https://github.com/tgstation/tgstation/pull/31950 - Removes an empty New() https://github.com/tgstation/tgstation/pull/31951 - Logs subsystem shutdowns --- code/__defines/_tick.dm | 2 +- code/__defines/math.dm | 4 ++ code/_helpers/mobs.dm | 4 +- code/_helpers/time.dm | 22 +++++++-- code/controllers/master.dm | 45 ++++++++++++------- code/controllers/subsystem.dm | 5 +-- code/modules/client/client procs.dm | 3 +- .../mob/living/simple_animal/simple_animal.dm | 2 +- 8 files changed, 59 insertions(+), 28 deletions(-) diff --git a/code/__defines/_tick.dm b/code/__defines/_tick.dm index 4dcbe76510..2c761b86f9 100644 --- a/code/__defines/_tick.dm +++ b/code/__defines/_tick.dm @@ -1,5 +1,5 @@ #define TICK_LIMIT_RUNNING 80 -#define TICK_LIMIT_TO_RUN 78 +#define TICK_LIMIT_TO_RUN 70 #define TICK_LIMIT_MC 70 #define TICK_LIMIT_MC_INIT_DEFAULT 98 diff --git a/code/__defines/math.dm b/code/__defines/math.dm index 4f1abc44ed..79dc1b4d24 100644 --- a/code/__defines/math.dm +++ b/code/__defines/math.dm @@ -8,3 +8,7 @@ //for when you need a reliable time number that doesn't depend on byond time. #define REALTIMEOFDAY (world.timeofday + (MIDNIGHT_ROLLOVER * MIDNIGHT_ROLLOVER_CHECK)) #define MIDNIGHT_ROLLOVER_CHECK ( rollovercheck_last_timeofday != world.timeofday ? update_midnight_rollover() : midnight_rollovers ) + +#define CEILING(x, y) ( -round(-(x) / (y)) * (y) ) +// round() acts like floor(x, 1) by default but can't handle other values +#define FLOOR(x, y) ( round((x) / (y)) * (y) ) diff --git a/code/_helpers/mobs.dm b/code/_helpers/mobs.dm index eb1ef73eb7..1dffebf607 100644 --- a/code/_helpers/mobs.dm +++ b/code/_helpers/mobs.dm @@ -182,7 +182,7 @@ Proc for attack log creation, because really why not var/starttime = world.time . = 1 while (world.time < endtime) - sleep(1) + stoplag(1) if (progress) progbar.update(world.time - starttime) if(!user || !target) @@ -229,7 +229,7 @@ Proc for attack log creation, because really why not var/starttime = world.time . = 1 while (world.time < endtime) - sleep(1) + stoplag(1) if (progress) progbar.update(world.time - starttime) diff --git a/code/_helpers/time.dm b/code/_helpers/time.dm index 2e2bf505ef..6dad42e128 100644 --- a/code/_helpers/time.dm +++ b/code/_helpers/time.dm @@ -13,6 +13,12 @@ #define TimeOfGame (get_game_time()) #define TimeOfTick (TICK_USAGE*0.01*world.tick_lag) +#define TICK *world.tick_lag +#define TICKS *world.tick_lag + +#define DS2TICKS(DS) (DS/world.tick_lag) // Convert deciseconds to ticks +#define TICKS2DS(T) (T TICKS) // Convert ticks to deciseconds + /proc/get_game_time() var/global/time_offset = 0 var/global/last_time = 0 @@ -111,13 +117,21 @@ var/round_start_time = 0 //Increases delay as the server gets more overloaded, //as sleeps aren't cheap and sleeping only to wake up and sleep again is wasteful -#define DELTA_CALC max(((max(TICK_USAGE, world.cpu) / 100) * max(Master.sleep_delta,1)), 1) +#define DELTA_CALC max(((max(TICK_USAGE, world.cpu) / 100) * max(Master.sleep_delta-1,1)), 1) -/proc/stoplag() +//returns the number of ticks slept +/proc/stoplag(initial_delay) + if (!Master || !(Master.current_runlevel & RUNLEVELS_DEFAULT)) + sleep(world.tick_lag) + return 1 + if (!initial_delay) + initial_delay = world.tick_lag . = 0 - var/i = 1 + var/i = DS2TICKS(initial_delay) do - . += round(i*DELTA_CALC) + . += CEILING(i*DELTA_CALC, 1) sleep(i*world.tick_lag*DELTA_CALC) i *= 2 while (TICK_USAGE > min(TICK_LIMIT_TO_RUN, Master.current_ticklimit)) + +#undef DELTA_CALC \ No newline at end of file diff --git a/code/controllers/master.dm b/code/controllers/master.dm index ef9189f944..3ecb3b7fcb 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -12,7 +12,7 @@ var/datum/controller/master/Master = new() name = "Master" // Are we processing (higher values increase the processing delay by n ticks) - var/processing = 1 + var/processing = TRUE // How many times have we ran var/iteration = 0 @@ -27,7 +27,7 @@ var/datum/controller/master/Master = new() var/init_time var/tickdrift = 0 - var/sleep_delta + var/sleep_delta = 1 var/make_runtime = 0 @@ -77,7 +77,9 @@ var/datum/controller/master/Master = new() sortTim(subsystems, /proc/cmp_subsystem_init) reverseRange(subsystems) for(var/datum/controller/subsystem/ss in subsystems) + log_world("Shutting down [ss.name] subsystem...") ss.Shutdown() + log_world("Shutdown complete") // Returns 1 if we created a new mc, 0 if we couldn't due to a recent restart, // -1 if we encountered a runtime trying to recreate it @@ -91,7 +93,7 @@ var/datum/controller/master/Master = new() var/delay = 50 * ++Master.restart_count Master.restart_timeout = world.time + delay Master.restart_clear = world.time + (delay * 2) - Master.processing = 0 //stop ticking this one + Master.processing = FALSE //stop ticking this one try new/datum/controller/master() catch @@ -267,35 +269,45 @@ var/datum/controller/master/Master = new() iteration = 1 var/error_level = 0 - var/sleep_delta = 0 + var/sleep_delta = 1 var/list/subsystems_to_check //the actual loop. + while (1) tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, (((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag))) + var/starting_tick_usage = TICK_USAGE if (processing <= 0) current_ticklimit = TICK_LIMIT_RUNNING sleep(10) continue - //if there are mutiple sleeping procs running before us hogging the cpu, we have to run later - // because sleeps are processed in the order received, so longer sleeps are more likely to run first - if (TICK_USAGE > TICK_LIMIT_MC) - sleep_delta += 2 + //Anti-tick-contention heuristics: + //if there are mutiple sleeping procs running before us hogging the cpu, we have to run later. + // (because sleeps are processed in the order received, longer sleeps are more likely to run first) + if (starting_tick_usage > TICK_LIMIT_MC) //if there isn't enough time to bother doing anything this tick, sleep a bit. + sleep_delta *= 2 current_ticklimit = TICK_LIMIT_RUNNING * 0.5 - sleep(world.tick_lag * (processing + sleep_delta)) + sleep(world.tick_lag * (processing * sleep_delta)) continue - sleep_delta = MC_AVERAGE_FAST(sleep_delta, 0) - if (last_run + (world.tick_lag * processing) > world.time) - sleep_delta += 1 - if (TICK_USAGE > (TICK_LIMIT_MC*0.5)) + //Byond resumed us late. assume it might have to do the same next tick + if (last_run + CEILING(world.tick_lag * (processing * sleep_delta), world.tick_lag) < world.time) sleep_delta += 1 + sleep_delta = MC_AVERAGE_FAST(sleep_delta, 1) //decay sleep_delta + + if (starting_tick_usage > (TICK_LIMIT_MC*0.75)) //we ran 3/4 of the way into the tick + sleep_delta += 1 + + //debug if (make_runtime) var/datum/controller/subsystem/SS SS.can_fire = 0 + if (!Failsafe || (Failsafe.processing_interval > 0 && (Failsafe.lasttick+(Failsafe.processing_interval*5)) < world.time)) new/datum/controller/failsafe() // (re)Start the failsafe. + + //now do the actual stuff if (!queue_head || !(iteration % 3)) var/checking_runlevel = current_runlevel if(cached_runlevel != checking_runlevel) @@ -312,6 +324,7 @@ var/datum/controller/master/Master = new() subsystems_to_check = current_runlevel_subsystems else subsystems_to_check = tickersubsystems + if (CheckQueue(subsystems_to_check) <= 0) if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems)) log_world("MC: SoftReset() failed, crashing") @@ -342,8 +355,10 @@ var/datum/controller/master/Master = new() iteration++ last_run = world.time src.sleep_delta = MC_AVERAGE_FAST(src.sleep_delta, sleep_delta) - current_ticklimit = TICK_LIMIT_RUNNING - (TICK_LIMIT_RUNNING * 0.25) //reserve the tail 1/4 of the next tick for the mc. - sleep(world.tick_lag * (processing + sleep_delta)) + current_ticklimit = TICK_LIMIT_RUNNING + if (processing * sleep_delta <= world.tick_lag) + current_ticklimit -= (TICK_LIMIT_RUNNING * 0.25) //reserve the tail 1/4 of the next tick for the mc if we plan on running next tick + sleep(world.tick_lag * (processing * sleep_delta)) diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm index 394786c710..bd9a2aeafb 100644 --- a/code/controllers/subsystem.dm +++ b/code/controllers/subsystem.dm @@ -31,8 +31,7 @@ var/static/list/failure_strikes //How many times we suspect a subsystem type has crashed the MC, 3 strikes and you're out! //Do not override -/datum/controller/subsystem/New() - return +///datum/controller/subsystem/New() // Used to initialize the subsystem BEFORE the map has loaded // Called AFTER Recover if that is called @@ -66,7 +65,7 @@ can_fire = 0 flags |= SS_NO_FIRE Master.subsystems -= src - + return ..() //Queue it to run. // (we loop thru a linked list until we get to the end or find the right point) diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index d085e5cec0..6a85924c4d 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -296,8 +296,7 @@ if (holder) sleep(1) else - sleep(5) - stoplag() + stoplag(5) /client/proc/last_activity_seconds() return inactivity / 10 diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 231db2866a..338b66e43f 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -1165,7 +1165,7 @@ //They ran away! else ai_log("AttackTarget() out of range!",3) - sleep(1) // Unfortunately this is needed to protect from ClosestDistance() sometimes not updating fast enough to prevent an infinite loop. + stoplag(1) // Unfortunately this is needed to protect from ClosestDistance() sometimes not updating fast enough to prevent an infinite loop. handle_stance(STANCE_ATTACK) return 0