Addressed some dependency loop issues

This commit is contained in:
Eric Froemling 2020-10-31 16:15:05 -05:00
parent cbebdfe285
commit 47ca132c33
17 changed files with 126 additions and 56 deletions

View File

@ -3932,24 +3932,24 @@
"assets/build/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/b5/85/f8b6d0558ddb87267f34254b1450",
"assets/build/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/1c/e1/4a1a2eddda2f4aebd5f8b64ab08e",
"assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f",
"build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/62/71/6b45cf5a4a3ed2c2f21853dd4ef5",
"build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8a/f5/60056a8ded058f143929f50aad4d",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8f/87/5297d9be37d6faed52315a4098b6",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/86/8e/418f87c669cbef6b031e84caa24f",
"build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f0/c3/17bd74632240ce445290ad1d1e6b",
"build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/13/5c/59fad7dcebaf88a92372b54ef35d",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b5/41/10ddaf61d0199cf19764b365233d",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/0c/62/1d6189df26dbc8293ccceae1763d",
"build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/d3/1f/5944ecb4991c8c7bbcf4cc5b28e8",
"build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a1/bc/9ac66ab00360dd4c9353e67fdabf",
"build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/e0/eb/0de6c758f379fc4d12ec30ed3e79",
"build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/33/ca/c57c3668af1215c22fc29a52dd4f",
"build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f4/dc/e060b50e18cf9ea793a8024c71ea",
"build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8c/ba/6ff3079ed32a9bcf0b19ffdd5014",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/91/f9/fb6d662e6a5b517405e952cebb47",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4f/bb/a249dad07ac24d901e263d4728ce",
"build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/8d/39/9484a05296f6c1815f48a19f7f80",
"build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/ce/13/3d5a5634dc85a9fff77f40eea079",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/64/a5/49ea3e527d625ec9891ac2d8dde8",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/62/13/c626103f654d4f30156b6440b547",
"build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/33/ce/f6ce9603a9f24fa46363c19b0995",
"build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/80/72/6844abdad72330180d9401023729",
"build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/d6/db/e9ca1e2278872e8d49614597ca41",
"build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/07/66/1eb6b6a7141a2d6a9a9dcb12573e",
"build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c3/d7/627d518a92951cf7fe9fc0b9b3a0",
"build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/de/49/2cfc34ac856737d903954db5571b",
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e8/9a/67cf9ece361ca2f5d338009bdbfc",
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/86/de/7d9c9a2b7bba34c630130ed759c9",
"build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/28/7e/3aa3d21bb007a3fd44fee67d03e2",
"build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/47/46/9a4ad3c7200e90fb58861ede9900",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3c/50/c9941b2059c3783861a6093f71a9",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/72/d9/4b66bf3eb0e3d0aa2e53de8d07e8"
"build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3f/20/a97fd2c5fd3a3f0332e4a822c112",
"build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d6/d2/6e5f87b787c1a33a3c1947f589e1",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a6/b6/e09b9690415a84f0602db60ef4de",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f0/f1/aa367945b9b426569dd8372a565e"
}

View File

@ -637,6 +637,7 @@
<w>evel</w>
<w>eventid</w>
<w>ewww</w>
<w>ewwww</w>
<w>excludepowerups</w>
<w>excludetypes</w>
<w>excstr</w>

View File

@ -55,7 +55,7 @@ from ba._dualteamsession import DualTeamSession
from ba._achievement import Achievement, AchievementSubsystem
from ba._appconfig import AppConfig
from ba._appdelegate import AppDelegate
from ba._apputils import is_browser_likely_available
from ba._apputils import is_browser_likely_available, garbage_collect
from ba._campaign import Campaign
from ba._gameutils import (GameTip, animate, animate_array, show_damage_count,
timestring, cameraflash)

View File

@ -3,6 +3,7 @@
"""Utility functionality related to the overall operation of the app."""
from __future__ import annotations
import gc
import os
from typing import TYPE_CHECKING
@ -150,9 +151,8 @@ def handle_leftover_log_file() -> None:
_error.print_exception('Error handling leftover log file.')
def garbage_collect(session_end: bool = True) -> None:
"""Run an explicit pass of garbage collection."""
import gc
def garbage_collect_session_end() -> None:
"""Run explicit garbage collection with extra checks for session end."""
gc.collect()
# Can be handy to print this to check for leaks between games.
@ -162,8 +162,19 @@ def garbage_collect(session_end: bool = True) -> None:
print('PYTHON GC FOUND', len(gc.garbage), 'UNCOLLECTIBLE OBJECTS:')
for i, obj in enumerate(gc.garbage):
print(str(i) + ':', obj)
if session_end:
print_live_object_warnings('after session shutdown')
print_live_object_warnings('after session shutdown')
def garbage_collect() -> None:
"""Run an explicit pass of garbage collection.
category: General Utility Functions
May also print warnings/etc. if collection takes too long or if
uncollectible objects are found (so use this instead of simply
gc.collect().
"""
gc.collect()
def print_live_object_warnings(when: Any,
@ -171,7 +182,6 @@ def print_live_object_warnings(when: Any,
ignore_activity: ba.Activity = None) -> None:
"""Print warnings for remaining objects in the current context."""
# pylint: disable=cyclic-import
import gc
from ba._session import Session
from ba._actor import Actor
from ba._activity import Activity

View File

@ -299,6 +299,27 @@ def verify_object_death(obj: object) -> None:
timetype=TimeType.REAL)
def print_active_refs(obj: Any) -> None:
"""Print info about things referencing a given object.
Category: General Utility Functions
Useful for tracking down cyclical references and causes for zombie objects.
"""
from types import FrameType
refs = list(gc.get_referrers(obj))
print(f'{Clr.YLW}Active referrers to {obj}:{Clr.RST}')
for i, ref in enumerate(refs):
print(f'{Clr.YLW}#{i+1}:{Clr.BLU} {ref}{Clr.RST}')
# For certain types of objects such as stack frames, show what is
# keeping *them* alive too.
if isinstance(ref, FrameType):
print(f'{Clr.YLW} Active referrers to #{i+1}:{Clr.RST}')
refs2 = list(gc.get_referrers(ref))
for j, ref2 in enumerate(refs2):
print(f'{Clr.YLW} #{j+1}:{Clr.BLU} {ref2}{Clr.RST}')
def _verify_object_death(wref: ReferenceType) -> None:
obj = wref()
if obj is None:
@ -312,12 +333,7 @@ def _verify_object_death(wref: ReferenceType) -> None:
print(f'{Clr.RED}Error: {name} not dying'
f' when expected to: {Clr.BLD}{obj}{Clr.RST}')
refs = list(gc.get_referrers(obj))
print(f'{Clr.YLW}Active References:{Clr.RST}')
i = 1
for ref in refs:
print(f'{Clr.YLW} reference {i}:{Clr.BLU} {ref}{Clr.RST}')
i += 1
print_active_refs(obj)
def storagename(suffix: str = None) -> str:

View File

@ -618,7 +618,7 @@ class Session:
# to run garbage collection to clear out any circular dependency
# loops. We keep this disabled normally to avoid non-deterministic
# hitches.
garbage_collect(session_end=False)
garbage_collect()
with _ba.Context(self):
if can_show_ad_on_death:

View File

@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, cast, Type
import _ba
from ba._enums import TimeType
from ba._general import print_active_refs
if TYPE_CHECKING:
from typing import Optional, List, Any
@ -216,7 +217,9 @@ def ui_upkeep() -> None:
print(
'WARNING:', obj,
'is still alive 5 second after its widget died;'
' you probably have a memory leak.')
' you might have a memory leak.')
print_active_refs(obj)
else:
remainingchecks.append(check)
ui.cleanupchecks = remainingchecks

View File

@ -285,6 +285,10 @@ class CoopBrowserWindow(ba.Window):
import bastd.ui.play as _unused10
def _update(self) -> None:
# Do nothing if we've somehow outlived our actual UI.
if not self._root_widget:
return
cur_time = ba.time(ba.TimeType.REAL)
# If its been a while since we got a tournament update, consider the

View File

@ -240,7 +240,6 @@ class GatherWindow(ba.Window):
ba.print_exception(f'Error saving state for {self}.')
def _restore_state(self) -> None:
# pylint: disable=too-many-branches
try:
for tab in self._tabs.values():
tab.restore_state()
@ -251,24 +250,17 @@ class GatherWindow(ba.Window):
assert isinstance(sel_name, (str, type(None)))
current_tab = self.TabID.ABOUT
gather_tab_val = ba.app.config.get('Gather Tab')
if bool(False):
# EWWW: normally would just do this, but it seems to result in
# a reference to self sticking around somewhere. (presumably
# in the exception?). Should get to the bottom of this.
try:
stored_tab = self.TabID(gather_tab_val)
if stored_tab in self._tab_row.tabs:
current_tab = stored_tab
except ValueError:
pass
else:
# Falling back to this for now.
tab_vals = {t.value for t in self.TabID}
if gather_tab_val in tab_vals:
stored_tab = self.TabID(gather_tab_val)
if stored_tab in self._tab_row.tabs:
current_tab = stored_tab
try:
stored_tab = self.TabID(gather_tab_val)
if stored_tab in self._tab_row.tabs:
current_tab = stored_tab
except ValueError:
# EWWWW; this exception causes a dependency loop that won't
# go away until the next cyclical collection, which can
# keep us alive. Perhaps should rethink our garbage
# collection strategy, but for now just explicitly running
# a cycle.
ba.pushcall(ba.garbage_collect)
self._set_tab(current_tab)
if sel_name == 'Back':
sel = self._back_button
@ -279,6 +271,12 @@ class GatherWindow(ba.Window):
sel_tab_id = self.TabID(sel_name.split(':')[-1])
except ValueError:
sel_tab_id = self.TabID.ABOUT
# EWWWW; this exception causes a dependency loop that won't
# go away until the next cyclical collection, which can
# keep us alive. Perhaps should rethink our garbage
# collection strategy, but for now just explicitly running
# a cycle.
ba.pushcall(ba.garbage_collect)
sel = self._tab_row.tabs[sel_tab_id].button
else:
sel = self._tab_row.tabs[current_tab].button

View File

@ -184,6 +184,12 @@ class ManualGatherTab(GatherTab):
try:
port = int(cast(str, ba.textwidget(query=port_textwidget)))
except ValueError:
# EWWWW; this exception causes a dependency loop that won't
# go away until the next cyclical collection, which can
# keep us alive. Perhaps should rethink our garbage
# collection strategy, but for now just explicitly running
# a cycle.
ba.pushcall(ba.garbage_collect)
port = -1
if port > 65535 or port < 0:
ba.screenmessage(ba.Lstr(resource='internal.invalidPortErrorText'),

View File

@ -1028,6 +1028,13 @@ class StoreBrowserWindow(ba.Window):
current_tab = self.TabID(ba.app.config.get('Store Tab'))
except ValueError:
current_tab = self.TabID.CHARACTERS
# EWWWW; this exception causes a dependency loop that won't
# go away until the next cyclical collection, which can keep
# us alive. Perhaps should rethink our garbage collection
# strategy, but for now just explicitly running a cycle.
ba.pushcall(ba.garbage_collect)
if self._show_tab is not None:
current_tab = self._show_tab
if sel_name == 'GetTickets' and self._get_tickets_button:

View File

@ -511,6 +511,12 @@ class WatchWindow(ba.Window):
try:
current_tab = self.TabID(ba.app.config.get('Watch Tab'))
except ValueError:
# EWWWW; this exception causes a dependency loop that won't
# go away until the next cyclical collection, which can
# keep us alive. Perhaps should rethink our garbage
# collection strategy, but for now just explicitly running
# a cycle.
ba.pushcall(ba.garbage_collect)
current_tab = self.TabID.MY_REPLAYS
self._set_tab(current_tab)
@ -522,6 +528,12 @@ class WatchWindow(ba.Window):
try:
sel_tab_id = self.TabID(sel_name.split(':')[-1])
except ValueError:
# EWWWW; this exception causes a dependency loop that won't
# go away until the next cyclical collection, which can
# keep us alive. Perhaps should rethink our garbage
# collection strategy, but for now just explicitly running
# a cycle.
ba.pushcall(ba.garbage_collect)
sel_tab_id = self.TabID.MY_REPLAYS
sel = self._tab_row.tabs[sel_tab_id].button
else:

View File

@ -13,8 +13,8 @@
<w>ack'ed</w>
<w>acked</w>
<w>acks</w>
<w>aclass</w>
<w>aclass's</w>
<w>aclass</w>
<w>activityplayer</w>
<w>addrs</w>
<w>adjoint</w>
@ -146,8 +146,8 @@
<w>cmath</w>
<w>cmds</w>
<w>cmdvals</w>
<w>codewarrior</w>
<w>codewarrior's</w>
<w>codewarrior</w>
<w>cofnodes</w>
<w>collapseable</w>
<w>collidable</w>

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2020-10-29 for Ballistica version 1.5.27 build 20234</em></h4>
<h4><em>last updated on 2020-10-31 for Ballistica version 1.5.27 build 20236</em></h4>
<p>This page documents the Python classes and functions in the 'ba' module,
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
<hr>
@ -86,6 +86,7 @@
<ul>
<li><a href="#function_ba_charstr">ba.charstr()</a></li>
<li><a href="#function_ba_do_once">ba.do_once()</a></li>
<li><a href="#function_ba_garbage_collect">ba.garbage_collect()</a></li>
<li><a href="#function_ba_getclass">ba.getclass()</a></li>
<li><a href="#function_ba_is_browser_likely_available">ba.is_browser_likely_available()</a></li>
<li><a href="#function_ba_is_point_in_box">ba.is_point_in_box()</a></li>
@ -6594,6 +6595,18 @@ method) and will convert it to a None value if it does not exist.
For more info, see notes on 'existables' here:
https://ballistica.net/wiki/Coding-Style-Guide</p>
<hr>
<h2><strong><a name="function_ba_garbage_collect">ba.garbage_collect()</a></strong></h3>
<p><span>garbage_collect() -&gt; None</span></p>
<p>Run an explicit pass of garbage collection.</p>
<p>Category: <a href="#function_category_General_Utility_Functions">General Utility Functions</a></p>
<p>May also print warnings/etc. if collection takes too long or if
uncollectible objects are found (so use this instead of simply
gc.collect().</p>
<hr>
<h2><strong><a name="function_ba_getactivity">ba.getactivity()</a></strong></h3>
<p><span>getactivity(doraise: bool = True) -&gt; &lt;varies&gt;</span></p>

View File

@ -21,7 +21,7 @@
namespace ballistica {
// These are set automatically via script; don't change here.
const int kAppBuildNumber = 20235;
const int kAppBuildNumber = 20236;
const char* kAppVersion = "1.5.27";
// Our standalone globals.

View File

@ -268,7 +268,7 @@ class Python {
kTranslateCall,
kLStrClass,
kCallClass,
kGarbageCollectCall,
kGarbageCollectSessionEndCall,
kConfig,
kOnAppLaunchCall,
kClientInfoQueryResponseCall,

View File

@ -103,7 +103,7 @@ def get_binding_values() -> object:
ba.app.lang.translate, # kTranslateCall
ba.Lstr, # kLStrClass
ba.Call, # kCallClass
_apputils.garbage_collect, # kGarbageCollectCall
_apputils.garbage_collect_session_end, # kGarbageCollectSessionEndCall
ba.ContextError, # kContextError
ba.NotFoundError, # kNotFoundError
ba.NodeNotFoundError, # kNodeNotFoundError