Python/Tkinter/tk crash [long]

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Eric Brunel

    Python/Tkinter/tk crash [long]

    Hi all,

    I was creating a Tkinter widget in the style of the reversed tabs below Excel
    worksheets and I stepped in a serious problem: the code I made makes python
    crash with a seg fault, bus error or X11 BadGC error on both Solaris (2.6 and
    2.7) and Linux (Mandrake 8.0); it doesn't crash on Windows. I tried to simplify
    the script, but I couldn't reproduce the crash with a simpler code. So the code
    below is somewhat long; sorry for that.

    To make it crash, just run the script and click on a tab. It usually crashes at
    the first click, but you may have to play a bit with the tabs. I tried to run
    Python through gdb to see where the crash happens, but it seems to be quite
    random. Maybe a memory corruption?

    There's a simple workaround, but with drawbacks: at the beginning of the
    __update method, if I do not destroy and re-create the Canvas, but simply empty
    its contents, the script works. But it keeps the commands declared created at
    the tcl level for the former bindings, so it silently eats up memory.

    My setup is Python 2.1 with tcl/tk 8.3.4. I searched the bug database but this
    bug doesn't seem to be known. But maybe it was corrected in a newer Python or tk
    version? Can anyone confirm that?

    Thanks a lot in advance.

    Here is the code:

    --TabRow.py----------------------------------
    from Tkinter import *


    ## CLASS GENERIC.CALLBAC K:
    ## =============== ========
    # Instances are generic callbacks for buttons, bindings, etc...

    class GenericCallback :

    def __init__(self, callback, *commonArgs):
    self.callback = callback
    self.__commonAr gs = commonArgs

    def __call__(self, *args):
    return apply(self.call back, self.__commonAr gs + args)



    ## CLASS TAB.ROW:
    ## ==============
    # Excel-style reverse tabs in a row

    class TabRow(Frame):

    ## CLASS ATTRIBUTES:
    ## -----------------

    defaultHeight = 24 # Default height for row
    verticalMargin = 2 # Margin at the top and the bottom of the tabs
    tabBorderWidth = 10 # Width for the descending and ascending lines at the
    # border of tabs


    ## METHOD __INIT__:
    ## ----------------
    # Constructor
    # Parameters: the ones for the Frame class
    # Recognized options: the ones for the Frame class +
    # - tabs: list of tabs
    # - currenttab: active element in tabs, or its index
    # - tabchangecallba ck: function called when the user clicks on a tab
    # (1 param = text; returns a boolean)
    # - font: the font to use to display the texts in the tabs
    # - activebackgroun d: the background for the current tab

    def __init__(self, *args, **options):

    ## INSTANCE ATTRIBUTES:
    ## --------------------

    self.__tabs = ()
    self.__tabChang eCallback = None
    self.__tabsCanv as = None
    self.__currentT abIndex = 0
    self.__tabPosit ions = None
    self.__canvasHe ight = TabRow.defaultH eight

    ## Super-init
    apply(Frame.__i nit__, (self,) + args)
    ## Configure/update
    self.configure( options)


    ## METHOD CONFIGURE:
    ## -----------------
    # Changes the options for the tab row

    def configure(self, dictOptions={}, **options):
    options.update( dictOptions)
    ## Get specific options
    self.__tabs = options.get("ta bs", self.__tabs)
    self.__tabChang eCallback = options.get('ta bchangecallback ',
    self.__tabChang eCallback)
    ## Get index for current tab
    if options.has_key ('currenttab'):
    if type(options['currenttab']) == type(0):
    self.__currentT abIndex = options['currenttab']
    else:
    indices = [i for i in range(len(self. __tabs))
    if self.__tabs[i] == options['currenttab']]
    if indices: self.__currentT abIndex = indices[0]
    ## Remember forced height for canvas if any
    self.__canvasHe ight = options.get('he ight', self.__canvasHe ight)
    ## Remove unwanted options
    needUpdate = 0
    for o in ('tabs', 'currenttab', 'tabchangecallb ack',
    'font', 'activebackgrou nd', 'height'):
    if not options.has_key (o): continue
    del options[o]
    needUpdate = 1
    if options.has_key ('bg') or options.has_key ('background'): needUpdate = 1
    ## If needed, apply options on the frame
    if options:
    apply(Frame.con figure, (self,), options)
    ## If needed, update display
    if needUpdate: self.__update()


    ## METHOD __UPDATE:
    ## ----------------
    # Updates the display

    def __update(self):
    ## (Re)create canvas for tabs
    if self.__tabsCanv as is not None:
    self.__tabsCanv as.grid_forget( )
    self.__tabsCanv as.destroy()
    self.__tabsCanv as = Canvas(self, bg=self.cget('b ackground'),
    height=self.__c anvasHeight)
    self.__tabsCanv as.grid(row=0, column=0, sticky='nswe')
    ## Build tabs
    tabIndex, pos = 0, 0
    self.__tabPosit ions = []
    activeTabRight = 0
    for text in self.__tabs:
    ## Standard tag + specific tag if tab is the current one
    tabTag = 'TAB_%s' % tabIndex
    tags = [tabTag]
    if tabIndex == self.__currentT abIndex: tags.append("CU RRENT_TAB")
    tags = tuple(tags)
    ## Remember tab position
    self.__tabPosit ions.append(pos )
    ## Draw text
    textId = self.__tabsCanv as.create_text( pos + TabRow.tabBorde rWidth,
    self.__canvasHe ight / 2,
    text=text, anchor=W, tags=tags,
    font=('helvetic a', 10, 'bold'))
    ## Polygon for tab, including line from left side if current tab
    textBBox = self.__tabsCanv as.bbox(textId)
    x = textBBox[2]
    coords = [
    pos, TabRow.vertical Margin,
    pos + TabRow.tabBorde rWidth,
    self.__canvasHe ight - TabRow.vertical Margin,
    x, self.__canvasHe ight - TabRow.vertical Margin,
    x + TabRow.tabBorde rWidth, TabRow.vertical Margin ]
    if tabIndex == self.__currentT abIndex:
    coords = [0, TabRow.vertical Margin] + coords
    activeTabRight = x + TabRow.tabBorde rWidth
    ## Get polygon background
    polygOpt = {'fill' : self.__tabsCanv as.cget('backgr ound'),
    'outline':'', 'tags':tags}
    if tabIndex == self.__currentT abIndex: polygOpt['fill'] = 'white'
    ## Draw polygon
    polygId = apply(self.__ta bsCanvas.create _polygon, coords, polygOpt)
    lineId = apply(self.__ta bsCanvas.create _line, coords,
    {'fill':'black' , 'tags':tags})
    ## Put it under text
    self.__tabsCanv as.lower(lineId )
    self.__tabsCanv as.lower(polygI d)
    ## Binding for tab change
    self.__tabsCanv as.tag_bind(tab Tag, '<ButtonReleas e-1>',
    GenericCallback (self.__changeT ab, tabIndex, text))
    ## Update position and tab index
    pos = x + TabRow.tabBorde rWidth / 2
    tabIndex += 1
    ## End of display: draw line from active tab to right border
    ## and put active tab on top
    self.__tabsCanv as.create_line( activeTabRight, TabRow.vertical Margin,
    pos + TabRow.tabBorde rWidth / 2,
    TabRow.vertical Margin)
    self.__tabsCanv as.tag_raise("C URRENT_TAB")


    ## METHOD __CHANGE.TAB:
    ## --------------------
    # Called when the user clicks on a tab

    def __changeTab(sel f, tabIndex, text, event=None):
    if self.__tabChang eCallback is None: return
    if not self.__tabChang eCallback(text) : return
    self.__currentT abIndex = tabIndex
    self.__update()



    if __name__ == '__main__':
    root = Tk()

    def ct(t):
    print t
    return 1

    r = TabRow(root, tabs=('foo', 'bar', 'spam'), tabchangecallba ck=ct)
    r.pack()
    root.mainloop()
    ---------------------------------------------
    --
    - Eric Brunel <eric.brunel@pr agmadev.com> -
    PragmaDev : Real Time Software Development Tools - http://www.pragmadev.com

  • Martin Franklin

    #2
    Re: Python/Tkinter/tk crash [long]

    On Thursday 14 August 2003 10:10 am, Eric Brunel wrote:[color=blue]
    > Hi all,
    >
    > I was creating a Tkinter widget in the style of the reversed tabs below
    > Excel worksheets and I stepped in a serious problem: the code I made makes
    > python crash with a seg fault, bus error or X11 BadGC error on both Solaris
    > (2.6 and 2.7) and Linux (Mandrake 8.0); it doesn't crash on Windows. I
    > tried to simplify the script, but I couldn't reproduce the crash with a
    > simpler code. So the code below is somewhat long; sorry for that.[/color]


    Eric,

    Have you looked at Pmw (Python Mega Widgets) they have a NoteBook (tabbed
    pane) widget that looks like what you want...


    Regards
    Martin



    Comment

    • Jeff Epler

      #3
      Re: Python/Tkinter/tk crash [long]

      I can duplicate the signal 11 on RedHat Linux 9 and the following
      relevant packages installed:
      python-2.2.2-26
      tcl-8.3.5-88
      tk-8.3.5-88

      My traceback looks like the following. I believe that the lines listed
      as "Tk_GetItemType s" actually correspond to static functions defined in
      tkCanvas.c.
      #0 0x4011b1a7 in Tk_GetItemTypes () from /usr/lib/libtk8.3.so
      #1 0x4011aec1 in Tk_GetItemTypes () from /usr/lib/libtk8.3.so
      #2 0x4011ac25 in Tk_GetItemTypes () from /usr/lib/libtk8.3.so
      #3 0x400d1a7c in Tk_HandleEvent () from /usr/lib/libtk8.3.so
      #4 0x400d1e7c in TkQueueEventFor AllChildren () from /usr/lib/libtk8.3.so
      #5 0x401c300d in Tcl_ServiceEven t () from /usr/lib/libtcl8.3.so
      #6 0x401c326d in Tcl_DoOneEvent () from /usr/lib/libtcl8.3.so
      #7 0x40062115 in Tkapp_MainLoop (self=0x815dfb0 , args=0x8269358) at Modules/_tkinter.c:1696
      #8 0x080d0df4 in PyCFunction_Cal l ()
      #9 0x0807a65e in PyEval_EvalCode ()
      #10 0x0807b0ce in PyEval_EvalCode Ex ()
      #11 0x0807c62b in PyEval_GetFuncD esc ()
      #12 0x0807a5a3 in PyEval_EvalCode ()
      #13 0x0807b0ce in PyEval_EvalCode Ex ()
      #14 0x08077fc5 in PyEval_EvalCode ()
      #15 0x08097e29 in PyRun_FileExFla gs ()
      #16 0x08096d90 in PyRun_SimpleFil eExFlags ()
      #17 0x080966da in PyRun_AnyFileEx Flags ()
      #18 0x08053a19 in Py_Main ()
      #19 0x08053469 in main ()
      #20 0x42015574 in __libc_start_ma in () from /lib/tls/libc.so.6

      This leads me to two things: first, reproduce this using a libtk
      compiled with debugging information. second, this is likely to be a Tk
      bug and not a Python bug given where it happens. It should be possible
      to write a test-case that is purely tcl code..

      In fact, the following tcl code crashed on me once (not in gdb, so I
      can't compare stack traces), and when I run it under valgrind the *first*
      click I make sprays "invalid read" and "invalid write" errors such as
      ==23796== Invalid write of size 4
      ==23796== at 0x4026ACEB: (within /usr/lib/libtk8.3.so)
      ==23796== by 0x4026AC24: (within /usr/lib/libtk8.3.so)
      ==23796== by 0x40221A7B: Tk_HandleEvent (in /usr/lib/libtk8.3.so)
      ==23796== by 0x40221E7B: (within /usr/lib/libtk8.3.so)
      ==23796== Address 0x41493D98 is 188 bytes inside a block of size 460 free'd
      ==23796== at 0x40161048: free (in /usr/lib/valgrind/valgrind.so)
      ==23796== by 0x402D8368: TclpFree (in /usr/lib/libtcl8.3.so)
      ==23796== by 0x402DD4F4: Tcl_Free (in /usr/lib/libtcl8.3.so)
      ==23796== by 0x402685C5: (within /usr/lib/libtk8.3.so)

      Now, in the python and wish cases, the shared libraries are loaded at
      different addresses. but note that the number of bytes between
      TK_HandleEvent and the next stack frame inwards (called Tk_GetItemTypes
      by gdb) is the same distance on my system.

      Jeff

      # --- tk program kinda like original python program
      proc p {} {
      puts p
      destroy .c
      canvas .c
      grid .c

      set tabIndex 0
      set pos 0
      foreach p { a b c } {
      set tags[list TAB_$tabIndex]
      if {$tabIndex == 1} {
      lappend tags CURRENT_TAB
      }
      set textId [.c create text $pos 10 -text $p -anchor w -tags $tags]
      .c bind TAB_$tabIndex <ButtonReleas e> p
      incr tabIndex 1
      incr pos 20
      }
      }
      p
      # ---

      Comment

      • Eric Brunel

        #4
        Re: Python/Tkinter/tk crash [long]

        Martin Franklin wrote:[color=blue]
        > On Thursday 14 August 2003 10:10 am, Eric Brunel wrote:
        >[color=green]
        >>Hi all,
        >>
        >>I was creating a Tkinter widget in the style of the reversed tabs below
        >>Excel worksheets and I stepped in a serious problem: the code I made makes
        >>python crash with a seg fault, bus error or X11 BadGC error on both Solaris
        >>(2.6 and 2.7) and Linux (Mandrake 8.0); it doesn't crash on Windows. I
        >>tried to simplify the script, but I couldn't reproduce the crash with a
        >>simpler code. So the code below is somewhat long; sorry for that.[/color]
        >
        >
        >
        > Eric,
        >
        > Have you looked at Pmw (Python Mega Widgets) they have a NoteBook (tabbed
        > pane) widget that looks like what you want...[/color]

        I do know and use extensively this package. The behaviour I'd like to implement
        is somewhat different than the one in Pmw.NoteBook. Please also note that
        despite its length, the code I posted is only an extract of the actual code.

        Thanks anyway.
        --
        - Eric Brunel <eric.brunel@pr agmadev.com> -
        PragmaDev : Real Time Software Development Tools - http://www.pragmadev.com

        Comment

        • Eric Brunel

          #5
          Re: Python/Tkinter/tk crash [long]

          Jeff Epler wrote:[color=blue]
          > I can duplicate the signal 11 on RedHat Linux 9 and the following
          > relevant packages installed:
          > python-2.2.2-26
          > tcl-8.3.5-88
          > tk-8.3.5-88
          >
          > My traceback looks like the following. I believe that the lines listed
          > as "Tk_GetItemType s" actually correspond to static functions defined in
          > tkCanvas.c.
          > #0 0x4011b1a7 in Tk_GetItemTypes () from /usr/lib/libtk8.3.so
          > #1 0x4011aec1 in Tk_GetItemTypes () from /usr/lib/libtk8.3.so
          > #2 0x4011ac25 in Tk_GetItemTypes () from /usr/lib/libtk8.3.so
          > #3 0x400d1a7c in Tk_HandleEvent () from /usr/lib/libtk8.3.so
          > #4 0x400d1e7c in TkQueueEventFor AllChildren () from /usr/lib/libtk8.3.so
          > #5 0x401c300d in Tcl_ServiceEven t () from /usr/lib/libtcl8.3.so
          > #6 0x401c326d in Tcl_DoOneEvent () from /usr/lib/libtcl8.3.so
          > #7 0x40062115 in Tkapp_MainLoop (self=0x815dfb0 , args=0x8269358) at Modules/_tkinter.c:1696
          > #8 0x080d0df4 in PyCFunction_Cal l ()
          > #9 0x0807a65e in PyEval_EvalCode ()
          > #10 0x0807b0ce in PyEval_EvalCode Ex ()
          > #11 0x0807c62b in PyEval_GetFuncD esc ()
          > #12 0x0807a5a3 in PyEval_EvalCode ()
          > #13 0x0807b0ce in PyEval_EvalCode Ex ()
          > #14 0x08077fc5 in PyEval_EvalCode ()
          > #15 0x08097e29 in PyRun_FileExFla gs ()
          > #16 0x08096d90 in PyRun_SimpleFil eExFlags ()
          > #17 0x080966da in PyRun_AnyFileEx Flags ()
          > #18 0x08053a19 in Py_Main ()
          > #19 0x08053469 in main ()
          > #20 0x42015574 in __libc_start_ma in () from /lib/tls/libc.so.6
          >
          > This leads me to two things: first, reproduce this using a libtk
          > compiled with debugging information. second, this is likely to be a Tk
          > bug and not a Python bug given where it happens. It should be possible
          > to write a test-case that is purely tcl code..[/color]

          Thanks a lot: you're absolutely right. The tcl code you wrote behaves exactly
          the same than the Python script I sent: when I run it, the first click on any
          text ends in a seg fault or a bus error. I should have tried to reproduce the
          bug in pure tcl, but I'm not really fluent in it...

          I'll try to figure out exactly what happens and submit the problem to the
          c.l.tcl newsgroup.

          Thanks!
          --
          - Eric Brunel <eric.brunel@pr agmadev.com> -
          PragmaDev : Real Time Software Development Tools - http://www.pragmadev.com

          Comment

          • Michele Simionato

            #6
            Re: Python/Tkinter/tk crash [long]

            Eric Brunel <eric.brunel@pr agmadev.com> wrote in message news:<bhfj7s$44 [email protected] .fr>...[color=blue]
            > Hi all,
            >
            > I was creating a Tkinter widget in the style of the reversed tabs below Excel
            > worksheets and I stepped in a serious problem: the code I made makes python
            > crash with a seg fault, bus error or X11 BadGC error on both Solaris (2.6 and
            > 2.7) and Linux (Mandrake 8.0); it doesn't crash on Windows.[/color]

            FWIW: it crashes under Red Hat 7.2+Python 2.2 and under Red Hat 7.3+Python 2.3
            too.

            Comment

            • Eric Brunel

              #7
              Re: Python/Tkinter/tk crash [long]

              Martin Franklin wrote:[color=blue]
              > On Thursday 14 August 2003 10:10 am, Eric Brunel wrote:[color=green]
              >>There's a simple workaround, but with drawbacks: at the beginning of the
              >>__update method, if I do not destroy and re-create the Canvas, but simply
              >>empty its contents, the script works. But it keeps the commands declared
              >>created at the tcl level for the former bindings, so it silently eats up
              >>memory.
              >>[/color]
              >
              >
              >
              > does it still eat up memory if you, for example, change the top of the
              > __update method to:
              >
              > def __update(self):
              > if not self.__tabsCanv as:
              > self.__tabsCanv as = Canvas(self, bg=self.cget('b ackground'),
              > height=self.__c anvasHeight)
              > self.__tabsCanv as.grid(row=0, column=0, sticky='nswe')
              > self.__tabsCanv as.delete("all" )[/color]

              Yes it does: the call to delete('all') on the canvas doesn't delete the
              callbacks registered at the tcl level for all the bindings done in the
              canvas. These commands are only deleted when the canvas itself is deleted.

              Consider the following script:

              --eatmem.py---------------------------
              from Tkinter import *

              root = Tk()

              cnv = Canvas(root)
              cnv.pack()

              def spam(*w): print spam

              def eat():
              cnv.delete(ALL)
              l = cnv.create_line (10, 10, 10, 30)
              cnv.tag_bind(l, '<1>', spam)
              root.after(100, eat)

              eat()

              root.mainloop()
              --------------------------------------

              If you run it and monitor the size of the running process either with the
              task manager on Windows or a "ps -A -o vsz -o args | grep eatmem' on Linux,
              you'll notice the memory occupied by the program slowly but steadily grows
              (you'll have to wait a little before it starts growing). Remove the bind, and
              it doesn't happen anymore.

              The same happens for commands in menus: they're only deleted when the whole
              menu goes away, not when the entry having the command is deleted.

              If you know something that can be done about that, I'm interested!
              --
              - Eric Brunel <eric.brunel@pr agmadev.com> -
              PragmaDev : Real Time Software Development Tools - http://www.pragmadev.com

              Comment

              • Pedro Rodriguez

                #8
                Re: Python/Tkinter/tk crash [long]

                On Thu, 14 Aug 2003 09:10:22 +0000, Eric Brunel wrote:
                [color=blue]
                > Hi all,
                >
                > I was creating a Tkinter widget in the style of the reversed tabs below Excel
                > worksheets and I stepped in a serious problem: the code I made makes python
                > crash with a seg fault, bus error or X11 BadGC error on both Solaris (2.6 and
                > 2.7) and Linux (Mandrake 8.0); it doesn't crash on Windows. I tried to simplify
                > the script, but I couldn't reproduce the crash with a simpler code. So the code
                > below is somewhat long; sorry for that.
                >
                > To make it crash, just run the script and click on a tab. It usually crashes at
                > the first click, but you may have to play a bit with the tabs. I tried to run
                > Python through gdb to see where the crash happens, but it seems to be quite
                > random. Maybe a memory corruption?
                >
                > There's a simple workaround, but with drawbacks: at the beginning of the
                > __update method, if I do not destroy and re-create the Canvas, but simply empty
                > its contents, the script works. But it keeps the commands declared created at
                > the tcl level for the former bindings, so it silently eats up memory.
                >[/color]

                As far as I understand, the __changeTab method is called when an event
                occurs on the __tabsCanvas. But one of the first actions of the __update
                method (which is called by __changeTab) is to destroy the __tabsCanvas
                object.

                So DURING the callback call you are destroying the object to which the
                callback is bound. I wonder if this is the cause of the blow up at the
                return of the callback (it may depend on how the memory is reallocated
                on different systems).

                Just my 0.02 euro guess

                --
                Pedro

                Comment

                • Eric Brunel

                  #9
                  Re: Python/Tkinter/tk crash [long]

                  Pedro Rodriguez wrote:[color=blue]
                  > On Thu, 14 Aug 2003 09:10:22 +0000, Eric Brunel wrote:
                  >
                  >[color=green]
                  >>Hi all,
                  >>
                  >>I was creating a Tkinter widget in the style of the reversed tabs below Excel
                  >>worksheets and I stepped in a serious problem: the code I made makes python
                  >>crash with a seg fault, bus error or X11 BadGC error on both Solaris (2.6 and
                  >>2.7) and Linux (Mandrake 8.0); it doesn't crash on Windows. I tried to simplify
                  >>the script, but I couldn't reproduce the crash with a simpler code. So the code
                  >>below is somewhat long; sorry for that.
                  >>
                  >>To make it crash, just run the script and click on a tab. It usually crashes at
                  >>the first click, but you may have to play a bit with the tabs. I tried to run
                  >>Python through gdb to see where the crash happens, but it seems to be quite
                  >>random. Maybe a memory corruption?
                  >>
                  >>There's a simple workaround, but with drawbacks: at the beginning of the
                  >>__update method, if I do not destroy and re-create the Canvas, but simply empty
                  >>its contents, the script works. But it keeps the commands declared created at
                  >>the tcl level for the former bindings, so it silently eats up memory.
                  >>[/color]
                  >
                  >
                  > As far as I understand, the __changeTab method is called when an event
                  > occurs on the __tabsCanvas. But one of the first actions of the __update
                  > method (which is called by __changeTab) is to destroy the __tabsCanvas
                  > object.
                  >
                  > So DURING the callback call you are destroying the object to which the
                  > callback is bound. I wonder if this is the cause of the blow up at the
                  > return of the callback (it may depend on how the memory is reallocated
                  > on different systems).[/color]

                  In fact, you're absolutely right, but it still shouldn't result in a crash. I
                  also discovered that the problem only happens with ButtonRelease events, and not
                  ButtonPress ones. Apparently, the tcl interpreter doesn't properly remembers
                  that the pointer on the canvas shouldn't be freed until the binding is
                  completely executed.

                  I've reported the problem to the comp.lang.tcl newsgroup. I hope they'll have a
                  solution for it, because I can't figure out by myself what I should do to
                  prevent the crash (I'm not that familiar with the code for tcl/tk). For the
                  moment, I'll use bindings on ButtonPress's rather than ButtonRelease's .

                  Thanks a lot to all who answered anyway!
                  --
                  - Eric Brunel <eric.brunel@pr agmadev.com> -
                  PragmaDev : Real Time Software Development Tools - http://www.pragmadev.com

                  Comment

                  Working...