prevent Job generator leak, added JobManager.finish(job), logging GarbageReports auto-destroy

This commit is contained in:
Darren Ranalli 2007-03-14 03:09:23 +00:00
parent e2ac31938f
commit 78b10c41ba
3 changed files with 54 additions and 17 deletions

View File

@ -4,7 +4,6 @@ __all__ = ['FakeObject', '_createGarbage', 'GarbageReport', 'GarbageLogger']
from direct.directnotify.DirectNotifyGlobal import directNotify from direct.directnotify.DirectNotifyGlobal import directNotify
from direct.showbase.PythonUtil import gcDebugOn, safeRepr, fastRepr from direct.showbase.PythonUtil import gcDebugOn, safeRepr, fastRepr
#from direct.showbase.TaskThreaded import TaskThreaded, TaskThread
from direct.showbase.Job import Job from direct.showbase.Job import Job
import gc import gc
@ -26,18 +25,18 @@ class GarbageReport(Job):
NotGarbage = 'NG' NotGarbage = 'NG'
def __init__(self, name, log=True, verbose=False, fullReport=False, findCycles=True, def __init__(self, name, log=True, verbose=False, fullReport=False, findCycles=True,
threaded=False, timeslice=None, doneCallback=None): threaded=False, doneCallback=None):
# if log is True, GarbageReport will self-destroy after logging # if log is True, GarbageReport will self-destroy after logging
# if false, caller is responsible for calling destroy() # if false, caller is responsible for calling destroy()
# if threaded is True, processing will be performed over multiple frames # if threaded is True, processing will be performed over multiple frames
Job.__init__(self, name) Job.__init__(self, name)
# stick the arguments onto a ScratchPad so we can access them from the thread # stick the arguments onto a ScratchPad so we can delete them all at once
# functions and delete them all at once
self._args = ScratchPad(name=name, log=log, verbose=verbose, fullReport=fullReport, self._args = ScratchPad(name=name, log=log, verbose=verbose, fullReport=fullReport,
findCycles=findCycles, doneCallback=doneCallback) findCycles=findCycles, doneCallback=doneCallback)
self._printing = False self._printing = False
jobMgr.add(self) jobMgr.add(self)
self.numGarbage = 0 if threaded == False:
jobMgr.finish(self)
def run(self): def run(self):
# do the garbage collection # do the garbage collection
@ -183,10 +182,20 @@ class GarbageReport(Job):
yield None yield None
self._printing = False self._printing = False
yield Job.Done
def suspend(self):
if self._printing:
self.notify.info('SUSPEND')
def resume(self):
if self._printing:
self.notify.info('RESUME')
def finished(self):
if self._args.doneCallback: if self._args.doneCallback:
self._args.doneCallback(self) self._args.doneCallback(self)
if self._args.log:
yield Job.Done self.destroy()
def destroy(self): def destroy(self):
#print 'GarbageReport.destroy' #print 'GarbageReport.destroy'
@ -204,13 +213,6 @@ class GarbageReport(Job):
del self._reportStr del self._reportStr
Job.destroy(self) Job.destroy(self)
def suspend(self):
if self._printing:
self.notify.info('SUSPEND')
def resume(self):
if self._printing:
self.notify.info('RESUME')
def getNumItems(self): def getNumItems(self):
return self.numGarbage return self.numGarbage

View File

@ -6,6 +6,7 @@ class Job:
Done = object() Done = object()
Continue = None # 'yield None' is acceptable in place of 'yield Job.Continue' Continue = None # 'yield None' is acceptable in place of 'yield Job.Continue'
# these priorities are reference points, you can use whatever numbers you want
Priorities = ScratchPad(Low=-100, Normal=0, High=100) Priorities = ScratchPad(Low=-100, Normal=0, High=100)
_SerialGen = SerialNumGen() _SerialGen = SerialNumGen()
@ -27,18 +28,21 @@ class Job:
def getPriority(self): def getPriority(self):
# override if you want a different priority # override if you want a different priority
# you can use numbers other than those in Job.Priorities
return Job.Priorities.Normal return Job.Priorities.Normal
def suspend(self): def suspend(self):
# called when JobManager is going to stop running this job for a while # called when JobManager is going to stop running this job for a while
# most jobs don't need to override this
pass pass
def resume(self): def resume(self):
# called when JobManager is going to start running this job again # called when JobManager is going to start running this job again
# most jobs don't need to override this
pass pass
def finished(self):
# called when the job finishes and has been removed from the JobManager
pass
def getJobName(self):
return self._name
def _getJobId(self): def _getJobId(self):
return self._id return self._id
@ -46,6 +50,9 @@ class Job:
if self._generator is None: if self._generator is None:
self._generator = self.run() self._generator = self.run()
return self._generator return self._generator
def _cleanupGenerator(self):
if self._generator is not None:
self._generator = None
if __debug__: # __dev__ not yet available at this point if __debug__: # __dev__ not yet available at this point
from direct.showbase.Job import Job from direct.showbase.Job import Job

View File

@ -59,6 +59,8 @@ class JobManager:
self._pri2jobIds[pri].remove(jobId) self._pri2jobIds[pri].remove(jobId)
# remove the job from the main table # remove the job from the main table
del self._pri2jobId2job[pri][jobId] del self._pri2jobId2job[pri][jobId]
# clean up the job's generator, if any
job._cleanupGenerator()
if len(self._pri2jobId2job[pri]) == 0: if len(self._pri2jobId2job[pri]) == 0:
del self._pri2jobId2job[pri] del self._pri2jobId2job[pri]
if pri == self._highestPriority: if pri == self._highestPriority:
@ -72,6 +74,31 @@ class JobManager:
taskMgr.remove(JobManager.TaskName) taskMgr.remove(JobManager.TaskName)
self._highestPriority = 0 self._highestPriority = 0
def finish(self, job):
# run this job, right now, until it finishes
assert self.notify.debugCall()
jobId = job._getJobId()
# look up the job's priority
pri = self._jobId2pri[jobId]
# grab the job
job = self._pri2jobId2job[pri][jobId]
gen = job._getGenerator()
job.resume()
while True:
try:
result = gen.next()
except StopIteration:
# Job didn't yield Job.Done, it ran off the end and returned
# treat it as if it returned Job.Done
self.notify.warning('job %s never yielded Job.Done' % job)
result = Job.Done
if result is Job.Done:
job.suspend()
self.remove(job)
job.finished()
# job is done.
break
# how long should we run per frame? # how long should we run per frame?
def getTimeslice(self): def getTimeslice(self):
return self._timeslice return self._timeslice
@ -104,6 +131,7 @@ class JobManager:
if result is Job.Done: if result is Job.Done:
job.suspend() job.suspend()
self.remove(job) self.remove(job)
job.finished()
# highest-priority job is done. # highest-priority job is done.
# grab the next one if there's time left # grab the next one if there's time left
break break