Be more robust with elapsed time counter 0.4.0
authorJeremy Stanley <fungi@yuggoth.org>
Mon, 26 Apr 2021 01:21:45 +0000 (01:21 +0000)
committerJeremy Stanley <fungi@yuggoth.org>
Mon, 26 Apr 2021 01:21:45 +0000 (01:21 +0000)
Stress testing identified a rare startup race if a user connected
before the elapsed time counter was initialized. The odds of
encountering this in the wild are close to nonexistent, but it was
introducing occasional test failures.

Solve the race by reordering the actions taken on each pulse, so
that the counter is initialized before the listening socket is
created. Also force all access to the counter through setter/getter
methods and wrap in try/except to always return a value and properly
create the counter and containing element if it doesn't yet exist.

mudpy/misc.py

index 6d61085..5270acd 100644 (file)
@@ -475,7 +475,19 @@ class Universe:
 
     def get_time(self):
         """Convenience method to get the elapsed time counter."""
-        return self.groups["internal"]["counters"].get("elapsed")
+        try:
+            return self.groups["internal"]["counters"].get("elapsed", 0)
+        except KeyError:
+            return 0
+
+    def set_time(self, elapsed):
+        """Convenience method to set the elapsed time counter."""
+        try:
+            self.groups["internal"]["counters"].set("elapsed", elapsed)
+        except KeyError:
+            # add an element for counters if it doesn't exist
+            Element("internal.counters", universe)
+            self.groups["internal"]["counters"].set("elapsed", elapsed)
 
     def add_group(self, group, fallback=None):
         """Set up group tracking/metadata."""
@@ -693,7 +705,7 @@ class User:
         if "$_(time)" in prompt:
             prompt = prompt.replace(
                 "$_(time)",
-                str(universe.groups["internal"]["counters"].get("elapsed")))
+                str(universe.get_time()))
 
         # Append a single space for clear separation from user input
         if prompt[-1] != " ":
@@ -1407,22 +1419,8 @@ def first_word(text, separator=" "):
 def on_pulse():
     """The things which should happen on each pulse, aside from reloads."""
 
-    # open the listening socket if it hasn't been already
-    if not hasattr(universe, "listening_socket"):
-        universe.initialize_server_socket()
-
-    # assign a user if a new connection is waiting
-    user = check_for_connection(universe.listening_socket)
-    if user:
-        universe.userlist.append(user)
-
-    # iterate over the connected users
-    for user in universe.userlist:
-        user.pulse()
-
-    # add an element for counters if it doesn't exist
-    if "counters" not in universe.groups.get("internal", {}):
-        Element("internal.counters", universe)
+    # increase the elapsed increment counter
+    universe.set_time(universe.get_time() + 1)
 
     # update the log every now and then
     if not universe.groups["internal"]["counters"].get("mark"):
@@ -1450,16 +1448,22 @@ def on_pulse():
             ) - 1
         )
 
+    # open the listening socket if it hasn't been already
+    if not hasattr(universe, "listening_socket"):
+        universe.initialize_server_socket()
+
+    # assign a user if a new connection is waiting
+    user = check_for_connection(universe.listening_socket)
+    if user:
+        universe.userlist.append(user)
+
+    # iterate over the connected users
+    for user in universe.userlist:
+        user.pulse()
+
     # pause for a configurable amount of time (decimal seconds)
     time.sleep(universe.contents["mudpy.timing"].get("increment"))
 
-    # increase the elapsed increment counter
-    universe.groups["internal"]["counters"].set(
-        "elapsed", universe.groups["internal"]["counters"].get(
-            "elapsed", 0
-        ) + 1
-    )
-
 
 def reload_data():
     """Reload all relevant objects."""