From 5490349f392375f394a2f7d706a995c4f1db4338 Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Fri, 23 Mar 2012 03:20:08 +0000 Subject: [PATCH] Regardless of the command-line flag there are a few methods that should always release the GIL, those that are expected to block or to take a long time. Add the annotations now in case I ever decide to change the default again. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxPython/Phoenix/trunk@70984 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- etg/app.py | 15 +++++-- etg/dialog.py | 3 ++ etg/event.py | 5 +++ etg/evtloop.py | 4 ++ etg/gdicmn.py | 7 +++- etg/msgdlg.py | 5 ++- etg/window.py | 6 +++ samples/simple/giltest.py | 85 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 samples/simple/giltest.py diff --git a/etg/app.py b/etg/app.py index dd414acb..7bb86000 100644 --- a/etg/app.py +++ b/etg/app.py @@ -51,7 +51,6 @@ def run(): c.find('HandleEvent').ignore() c.find('UsesEventLoop').ignore() - # We will use OnAssertFailure, but I don't think we should let it be # overridden in Python. @@ -65,8 +64,12 @@ def run(): c.find('OnFatalException').ignore() c.find('OnUnhandledException').ignore() - c.find('ExitMainLoop').isVirtual = False + c.find('ExitMainLoop').isVirtual = False + # Release the GIL for potentially blocking or long-running functions + c.find('MainLoop').releaseGIL() + c.find('ProcessPendingEvents').releaseGIL() + c.find('Yield').releaseGIL() c.addProperty('AppDisplayName GetAppDisplayName SetAppDisplayName') c.addProperty('AppName GetAppName SetAppName') @@ -116,7 +119,7 @@ def run(): # TODO: Add them as etg method objects instead of a WigCode block so the # documentation generators will see them too c.addItem(etgtools.WigCode("""\ - virtual int MainLoop(); + virtual int MainLoop() /ReleaseGIL/; virtual void OnPreInit(); virtual bool OnInit(); virtual bool OnInitGui(); @@ -168,6 +171,9 @@ def run(): display, or whatever the equivallent is for the platform.""", className=c.name)) + # Release the GIL for potentially blocking or long-running functions + c.find('SafeYield').releaseGIL() + c.find('SafeYieldFor').releaseGIL() c.addProperty('AssertMode GetAssertMode SetAssertMode') c.addProperty('DisplayMode GetDisplayMode SetDisplayMode') @@ -204,6 +210,9 @@ def run(): f.detailedDoc = [] + module.find('wxYield').releaseGIL() + module.find('wxSafeYield').releaseGIL() + #------------------------------------------------------- # Now add extractor objects for the main App class as a Python class, diff --git a/etg/dialog.py b/etg/dialog.py index 86a3a010..3cbfaa1f 100644 --- a/etg/dialog.py +++ b/etg/dialog.py @@ -49,6 +49,9 @@ def run(): # doc this method then... c.find('SetModal').ignore() + # Release the GIL for potentially blocking or long-running functions + c.find('ShowModal').releaseGIL() + # context manager methods c.addPyMethod('__enter__', '(self)', 'return self') c.addPyMethod('__exit__', '(self, exc_type, exc_val, exc_tb)', 'self.Destroy()') diff --git a/etg/event.py b/etg/event.py index fff719bd..f1ff19ca 100644 --- a/etg/event.py +++ b/etg/event.py @@ -245,6 +245,11 @@ def run(): tools.removeVirtuals(c) c.find('ProcessEvent').isVirtual = True + # Release the GIL for potentially blocking or long-running functions + c.find('ProcessEvent').releaseGIL() + c.find('ProcessEventLocally').releaseGIL() + c.find('SafelyProcessEvent').releaseGIL() + c.find('ProcessPendingEvents').releaseGIL() c.addPyMethod('Bind', '(self, event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)', diff --git a/etg/evtloop.py b/etg/evtloop.py index 89bdd616..13d59cf4 100644 --- a/etg/evtloop.py +++ b/etg/evtloop.py @@ -38,6 +38,10 @@ def run(): c = module.find('wxEventLoopBase') assert isinstance(c, etgtools.ClassDef) c.abstract = True + + c.find('Yield').releaseGIL() + c.find('YieldFor').releaseGIL() + c = module.find('wxEventLoopActivator') c.addPrivateAssignOp() diff --git a/etg/gdicmn.py b/etg/gdicmn.py index a025ee5d..04134134 100644 --- a/etg/gdicmn.py +++ b/etg/gdicmn.py @@ -130,7 +130,12 @@ def run(): #--------------------------------------- # wxSize tweaks c = module.find('wxSize') - + + # Used for testing releaseing or holding the GIL in giltest.py + #c.find('wxSize').findOverload('int width, int height').releaseGIL() + #c.find('DecBy').findOverload('int dx, int dy').releaseGIL() + #c.find('IncBy').findOverload('int dx, int dy').releaseGIL() + c.addProperty("width GetWidth SetWidth") c.addProperty("height GetHeight SetHeight") diff --git a/etg/msgdlg.py b/etg/msgdlg.py index 2b549020..808ec65d 100644 --- a/etg/msgdlg.py +++ b/etg/msgdlg.py @@ -53,10 +53,11 @@ def run(): c.find('SetYesNoLabels.yes').type = 'const wxString&' c.find('SetYesNoLabels.no').type = 'const wxString&' - - + tools.fixTopLevelWindowClass(c) + module.find('wxMessageBox').releaseGIL() + #----------------------------------------------------------------- tools.doCommonTweaks(module) tools.runGenerators(module) diff --git a/etg/window.py b/etg/window.py index 5ce31c39..57134067 100644 --- a/etg/window.py +++ b/etg/window.py @@ -70,6 +70,12 @@ def run(): c.find('GetScreenPosition').findOverload('int *').ignore() c.find('ClientToScreen').findOverload('int *').ignore() c.find('ScreenToClient').findOverload('int *').ignore() + + # Release the GIL for potentially blocking or long-running functions + c.find('PopupMenu').releaseGIL() + c.find('ProcessEvent').releaseGIL() + c.find('ProcessWindowEvent').releaseGIL() + c.find('ProcessWindowEventLocally').releaseGIL() # Add a couple wrapper functions for symmetry with the getters of the same name c.addPyMethod('SetRect', '(self, rect)', 'return self.SetSize(rect)') diff --git a/samples/simple/giltest.py b/samples/simple/giltest.py new file mode 100644 index 00000000..5ce39a10 --- /dev/null +++ b/samples/simple/giltest.py @@ -0,0 +1,85 @@ +# +# A simple test to verify that the GIL is released while in long running +# wrappers like MainLoop, ShowModal, and PopupMenu so background threads can +# be allowed to run at those times. +# + +import wx +import threading +import time +import random + +print wx.version() + + +class ThreadedTask(threading.Thread): + def __init__(self, *args, **kw): + threading.Thread.__init__(self, *args, **kw) + self.counter = 0 + self.sleepTime = random.random()/2 + self.timeToDie = False + + def run(self): + while not self.timeToDie: + time.sleep(self.sleepTime) + self.counter += 1 + print 'thread: %5s count: %d' % (self.name, self.counter) + + + +class MainFrame(wx.Frame): + def __init__(self): + wx.Frame.__init__(self, None, title="GIL Test") + self.pnl = wx.Panel(self) + btn = wx.Button(self.pnl, label='modal dialog', pos=(10,10)) + self.Bind(wx.EVT_BUTTON, self.onButton, btn) + self.pnl.Bind(wx.EVT_CONTEXT_MENU, self.onShowMenu) + btn = wx.Button(self.pnl, label='timed test', pos=(10, 60)) + self.Bind(wx.EVT_BUTTON, self.onOtherButton, btn) + + + def onButton(self, evt): + dlg = wx.Dialog(self, title='close this dialog', size=(300,150)) + dlg.ShowModal() + dlg.Destroy() + + + def onOtherButton(self, evt): + # A simplistic benchmark test that times many repititions of some + # simple operations so they can be tested with and without releasing + # the GIL + start = time.time() + reps = 100000 + for x in range(reps): + s = wx.Size(100, 100) + for n in range(10): + s.DecBy(4,6) + for n in range(10): + s.IncBy(4,6) + wx.MessageBox('%d reps performed in %f seconds' % (reps, time.time() - start), + 'Results') + + + def onShowMenu(self, evt): + menu = wx.Menu() + menu.Append(-1, 'one') + menu.Append(-1, 'two') + menu.Append(-1, 'three') + self.pnl.PopupMenu(menu) + menu.Destroy() + + + +threads = [ ThreadedTask(name='one'), ThreadedTask(name='two'), ThreadedTask(name='three') ] +for t in threads: + t.start() + +app = wx.App() +frm = MainFrame() +frm.Show() +app.MainLoop() + +for t in threads: + t.timeToDie = True + + \ No newline at end of file