Menu

#2693 With wxt, "gnuplot -persist" hangs if gnuplot cannot draw before stdin is closed

None
closed-fixed
nobody
None
2024-12-21
2024-02-29
No

With gnuplot 6.0.0 under Debian/unstable, using the FVWM window manager and its ManualPlacement configuration, and the wxt terminal, if I run
{ echo 'plot x' ; sleep 3 ; } | gnuplot -persist
there are no issues if I place the window before the 3 seconds, but if I place the window after the 3 seconds, gnuplot hangs and the window gets never drawn (its permanently gets the contents of what's behind the window).

Note that
{ echo 'plot x' ; exec >&- ; sleep 3 ; } | gnuplot -persist
triggers the problem even if I place the window before the 3 seconds.

If I run gnuplot via ssh (even ssh localhost) or if I use strace to try to debug, the problem disappears. So this seems to be a race condition.

FVWM's ManualPlacement is described as

ManualPlacement (aka active placement).  The user is required to
place every new window manually.  The window  only  shows  as  a
rubber  band  until a place is selected manually.  The window is
placed when a mouse button or any key except Escape is  pressed.
Escape  aborts  manual  placement which places the window in the
top left corner of the screen.  If mouse  button  2  is  pressed
during the initial placement of a window (respectively Shift and
mouse  button  1 in case Mwm emulation has been enabled with the
Emulate command), the user is asked to resize the window too.

When the problem occurs, using FVWM's Delete command to remove the window does not have any effect (so this is not just a display issue, the process is frozen in some state); FVWM's Destroy command works, but the "gnuplot -persist" process is still running. These commands are described as follows:

Delete
    Sends a message to a window asking that it remove itself,
    frequently causing the application to exit.

Destroy
    Destroys an application window, which usually causes the
    application to crash and burn.

My Debian bug report and various information:
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1064982

Discussion

  • Vincent Lefevre

    Vincent Lefevre - 2024-02-29

    Actually, the problem also occurs with gnuplot 5. It has actually appeared with the 1.52 pango library: downgrading the pango packages to 1.51 makes the problem disappear, both with gnuplot 5 and 6. Now, I don't know whether this is a bug in pango or a bug in gnuplot that is triggered only with the new version of pango (for instance, it might be possible that pango 1.52 is faster than 1.51, making the problem appear, since it seems to be a race condition).

     
  • Vincent Lefevre

    Vincent Lefevre - 2024-02-29

    I've identified the "problematic" commit in Pango:
    https://gitlab.gnome.org/GNOME/pango/-/commit/89442dae443eba2aa0f0a526b4d6d39c0c9b13c6

    The commit message is

    Just use a single fontconfig thread
    
    In ac4d8f2964299d14046cb, we switched from using
    GTask to using explicit threading for our fontconfig
    calls, since GTask assumes a mainloop and leaks memory
    otherwise. But now we are creating a new thread for
    every single fontconfig call, which is a bit excessive.
    As a compromise, create a single fontconfig thread
    per fontmap, and use an async queue to send fontconfig
    calls to that thread.
    

    Thus it is possible that this change makes Pango faster, so that the problem now appears in gnuplot (as slowing gnuplot down makes the problem disappear). In this case, this would not be a bug in Pango, just that the old Pango code was hiding the bug in gnuplot.

    Just in case, I've also opened an issue in Pango:
    https://gitlab.gnome.org/GNOME/pango/-/issues/784

     
  • Vincent Lefevre

    Vincent Lefevre - 2024-02-29

    This problem actually appears even without FVWM's ManualPlacement, i.e. if the window is displayed immediately. So I suppose that it is likely to occur with other window managers.

     
  • Vincent Lefevre

    Vincent Lefevre - 2024-02-29

    So, a simple command to reproduce the issue:
    echo 'set terminal wxt; plot x' | gnuplot -persist

     
  • Ethan Merritt

    Ethan Merritt - 2024-03-04

    My machines do not yet have pango 1.52 so I cannot directly test this.

    Can you get a stack trace from gnuplot while it is in the hung state?
    For instance use ps to find the pid for the gnuplot process; connect to it with gdb -p <pid>; type where to generate a stack trace.

     
  • Vincent Lefevre

    Vincent Lefevre - 2024-08-28

    I did not see this message, grrr... Here's the stack trace:

    #0  syscall () at ../sysdeps/unix/sysv/linux/x86_64/syscall.S:38
    #1  0x00007f60d833ea84 in g_cond_wait_impl (cond=0x55a694e3ab80, mutex=0x55a694e3ab78) at ../../../glib/gthread-posix.c:1007
    #2  g_cond_wait (cond=cond@entry=0x55a694e3ab80, mutex=mutex@entry=0x55a694e3ab78) at ../../../glib/gthread.c:1665
    #3  0x00007f60d77a42e3 in pango_fc_patterns_get_font_pattern (pats=0x55a694e3ab70, i=0, prepare=<synthetic pointer>) at ../pango/pangofc-fontmap.c:1136
    #4  pango_fc_fontset_load_next_font (fontset=0x55a694e401d0 [PangoFcFontset]) at ../pango/pangofc-fontmap.c:1248
    #5  pango_fc_fontset_get_font_at (fontset=fontset@entry=0x55a694e401d0 [PangoFcFontset], i=i@entry=0) at ../pango/pangofc-fontmap.c:1278
    #6  0x00007f60d77a480a in pango_fc_fontset_foreach (fontset=0x55a694e401d0 [PangoFcFontset], func=0x7f60d87b4e90 <get_font_foreach>, data=0x7ffc36945ef0) at ../pango/pangofc-fontmap.c:1401
    #7  0x00007f60d87b5adc in get_font (state=state@entry=0x7ffc36945fc0, wc=wc@entry=103, font=font@entry=0x7ffc36945fb8, position=position@entry=0x7ffc36945fb4) at ../pango/itemize.c:796
    #8  0x00007f60d87b6417 in itemize_state_process_run (state=0x7ffc36945fc0) at ../pango/itemize.c:984
    #9  pango_itemize_with_font (context=<optimized out>, base_dir=base_dir@entry=PANGO_DIRECTION_LTR, text=0x55a694b94fd0 "g", start_index=0, length=<optimized out>, attrs=attrs@entry=0x55a694cd0190, cached_iter=0x7ffc36946790, desc=0x0) at ../pango/itemize.c:1570
    #10 0x00007f60d87c6c80 in pango_layout_check_lines (layout=layout@entry=0x55a694e37f10 [PangoLayout]) at ../pango/pango-layout.c:4900
    #11 0x00007f60d87ca16e in pango_layout_check_lines (layout=<optimized out>) at ../pango/pango-layout.c:4792
    #12 pango_layout_get_extents_internal (layout=0x55a694e37f10 [PangoLayout], ink_rect=ink_rect@entry=0x0, logical_rect=logical_rect@entry=0x7ffc369469a0, line_extents=line_extents@entry=0x0) at ../pango/pango-layout.c:2925
    #13 0x00007f60d87ca40c in pango_layout_get_pixel_size (layout=<optimized out>, width=0x7ffc36946a1c, height=0x7ffc36946a28) at ../pango/pango-layout.c:3194
    #14 0x00007f60d8cd08f9 in wxCairoContext::GetTextExtent (this=<optimized out>, str=<optimized out>, width=0x0, height=0x7ffc36946ad0, descent=0x0, externalLeading=<optimized out>) at ./src/generic/graphicc.cpp:2929
    #15 0x00007f60d8c88f4d in wxGCDCImpl::DoGetTextExtent (this=this@entry=0x55a69495f430, str=..., width=width@entry=0x0, height=height@entry=0x7ffc36946b4c, descent=descent@entry=0x0, externalLeading=externalLeading@entry=0x0, theFont=0x0) at ./src/common/dcgraph.cpp:1257
    #16 0x00007f60d8c8e37f in wxGCDCImpl::GetCharHeight (this=0x55a69495f430) at ./src/common/dcgraph.cpp:1308
    #17 0x00007f60d8be5ab6 in wxDC::GetCharHeight (this=0x7ffc36946be0) at ./include/wx/dc.h:962
    #18 wxStatusBar::OnPaint (this=0x55a694b189a0) at ./src/generic/statusbr.cpp:449
    #19 0x00007f60d862a942 in wxEvtHandler::ProcessEventIfMatchesId (entry=<optimized out>, handler=handler@entry=0x55a694b189a0, event=...) at ./src/common/event.cpp:1482
    #20 0x00007f60d862ab0b in wxEventHashTable::HandleEvent (this=<optimized out>, event=..., self=self@entry=0x55a694b189a0) at ./src/common/event.cpp:1087
    #21 0x00007f60d862b17d in wxEvtHandler::TryHereOnly (this=this@entry=0x55a694b189a0, event=...) at ./src/common/event.cpp:1679
    #22 0x00007f60d862b1fe in wxEvtHandler::TryBeforeAndHere (event=..., this=0x55a694b189a0) at ./include/wx/event.h:4007
    #23 wxEvtHandler::ProcessEventLocally (this=0x55a694b189a0, event=...) at ./src/common/event.cpp:1612
    #24 0x00007f60d862b301 in wxEvtHandler::ProcessEvent (this=0x55a694b189a0, event=...) at ./src/common/event.cpp:1585
    #25 0x00007f60d862c48b in wxEvtHandler::SafelyProcessEvent (this=<optimized out>, event=...) at ./src/common/event.cpp:1701
    #26 0x00007f60d8d944b0 in wxWindowBase::HandleWindowEvent (this=this@entry=0x55a694b189a0, event=...) at ./src/common/wincmn.cpp:1553
    #27 0x00007f60d8baa632 in wxWindow::GTKSendPaintEvents (this=this@entry=0x55a694b189a0, cr=cr@entry=0x55a694e94da0) at ./src/gtk/window.cpp:5531
    #28 0x00007f60d8baa9db in draw (cr=0x55a694e94da0, win=0x55a694b189a0) at ./src/gtk/window.cpp:524
    #29 0x00007f60d6097caa in _gtk_marshal_BOOLEAN__BOXED (closure=closure@entry=0x55a694b13a20, return_value=return_value@entry=0x7ffc36947060, n_param_values=n_param_values@entry=2, param_values=param_values@entry=0x7ffc369470f0, invocation_hint=invocation_hint@entry=0x7ffc36947040, marshal_data=marshal_data@entry=0x0) at gtk/gtkmarshalers.c:84
    #35 0x00007f60d8760723 in <emit signal 'draw' on instance 0x55a694b13820 [wxPizza]> (instance=instance@entry=0x55a694b13820, signal_id=<optimized out>, detail=detail@entry=0) at ../../../gobject/gsignal.c:3582
        #30 0x00007f60d63534c3 in gtk_widget_draw_marshaller (closure=0x55a694b13a20, return_value=0x7ffc36947060, n_param_values=2, param_values=0x7ffc369470f0, invocation_hint=0x7ffc36947040, marshal_data=0x0) at ../../../gtk/gtkwidget.c:951
        #31 0x00007f60d87449c0 in g_closure_invoke (closure=0x55a694b13a20, return_value=0x7ffc36947060, n_param_values=2, param_values=0x7ffc369470f0, invocation_hint=0x7ffc36947040) at ../../../gobject/gclosure.c:833
        #32 0x00007f60d8758d83 in signal_emit_unlocked_R (node=node@entry=0x7ffc369471d0, detail=detail@entry=0, instance=instance@entry=0x55a694b13820, emission_return=emission_return@entry=0x7ffc36947250, instance_and_params=instance_and_params@entry=0x7ffc369470f0) at ../../../gobject/gsignal.c:3887
        #33 0x00007f60d875a072 in signal_emit_valist_unlocked (instance=instance@entry=0x55a694b13820, signal_id=signal_id@entry=52, detail=detail@entry=0, var_args=var_args@entry=0x7ffc36947330) at ../../../gobject/gsignal.c:3532
        #34 0x00007f60d8760666 in g_signal_emit_valist (instance=0x55a694b13820, signal_id=52, detail=0, var_args=0x7ffc36947330) at ../../../gobject/gsignal.c:3262
    #36 0x00007f60d635b192 in gtk_widget_draw_internal (widget=widget@entry=0x55a694b13820 [wxPizza], cr=cr@entry=0x55a694e94da0, clip_to_size=clip_to_size@entry=1) at ../../../gtk/gtkwidget.c:7077
    #37 0x00007f60d612202d in gtk_container_propagate_draw (container=<optimized out>, child=0x55a694b13820 [wxPizza], cr=0x55a694e94da0) at ../../../gtk/gtkcontainer.c:3854
    #38 0x00007f60d612213d in gtk_container_draw (widget=0x55a694a5e280 [GtkBox], cr=cr@entry=0x55a694e94da0) at ../../../gtk/gtkcontainer.c:3674
    #39 0x00007f60d60d17b8 in gtk_box_draw_contents (gadget=<optimized out>, cr=0x55a694e94da0, x=<optimized out>, y=<optimized out>, width=<optimized out>, height=<optimized out>, unused=0x0) at ../../../gtk/gtkbox.c:453
    #40 0x00007f60d6128881 in gtk_css_custom_gadget_draw (gadget=<optimized out>, cr=<optimized out>, x=<optimized out>, y=<optimized out>, width=<optimized out>, height=<optimized out>) at ../../../gtk/gtkcsscustomgadget.c:159
    #41 0x00007f60d612f439 in gtk_css_gadget_draw (gadget=0x55a694ab2e20 [GtkCssCustomGadget], cr=0x55a694e94da0) at ../../../gtk/gtkcssgadget.c:885
    #42 0x00007f60d60d2c95 in gtk_box_draw (widget=<optimized out>, cr=<optimized out>) at ../../../gtk/gtkbox.c:462
    #43 0x00007f60d635b07c in gtk_widget_draw_internal (widget=widget@entry=0x55a694a5e280 [GtkBox], cr=cr@entry=0x55a694e94da0, clip_to_size=clip_to_size@entry=1) at ../../../gtk/gtkwidget.c:7084
    #44 0x00007f60d612202d in gtk_container_propagate_draw (container=<optimized out>, child=0x55a694a5e280 [GtkBox], cr=0x55a694e94da0) at ../../../gtk/gtkcontainer.c:3854
    #45 0x00007f60d612213d in gtk_container_draw (widget=0x55a6949b1820 [GtkWindow], cr=0x55a694e94da0) at ../../../gtk/gtkcontainer.c:3674
    #46 0x00007f60d635b07c in gtk_widget_draw_internal (widget=widget@entry=0x55a6949b1820 [GtkWindow], cr=cr@entry=0x55a694e94da0, clip_to_size=clip_to_size@entry=1) at ../../../gtk/gtkwidget.c:7084
    #47 0x00007f60d6369810 in gtk_widget_render (widget=0x55a6949b1820 [GtkWindow], window=0x55a694e8c660 [GdkX11Window], region=<optimized out>) at ../../../gtk/gtkwidget.c:17610
    #48 0x00007f60d6207cb5 in gtk_main_do_event (event=0x7ffc36947820) at ../../../gtk/gtkmain.c:1844
    #49 gtk_main_do_event (event=<optimized out>) at ../../../gtk/gtkmain.c:1691
    #50 0x00007f60d68aece9 in _gdk_event_emit (event=event@entry=0x7ffc36947820) at ../../../gdk/gdkevents.c:73
    #51 0x00007f60d68bde79 in _gdk_window_process_updates_recurse_helper (window=0x55a694e8c660 [GdkX11Window], expose_region=<optimized out>) at ../../../gdk/gdkwindow.c:3874
    #52 0x00007f60d68bfa76 in gdk_window_process_updates_internal (window=window@entry=0x55a694e8c660 [GdkX11Window]) at ../../../gdk/gdkwindow.c:4020
    #53 0x00007f60d68bfc8c in gdk_window_process_updates_with_mode (window=<optimized out>, recurse_mode=<optimized out>) at ../../../gdk/gdkwindow.c:4215
    #54 gdk_window_process_updates_with_mode (window=<optimized out>, recurse_mode=<optimized out>) at ../../../gdk/gdkwindow.c:4186
    #58 0x00007f60d8760723 in <emit signal 'paint' on instance 0x55a694e8c950 [GdkFrameClockIdle]> (instance=instance@entry=0x55a694e8c950, signal_id=<optimized out>, detail=detail@entry=0) at ../../../gobject/gsignal.c:3582
        #55 0x00007f60d8744bc9 in _g_closure_invoke_va (closure=0x55a694e8ca20, return_value=0x0, instance=0x55a694e8c950, args=0x7ffc36947bc0, n_params=0, param_types=0x0) at ../../../gobject/gclosure.c:896
        #56 0x00007f60d875a8f8 in signal_emit_valist_unlocked (instance=instance@entry=0x55a694e8c950, signal_id=signal_id@entry=32, detail=detail@entry=0, var_args=var_args@entry=0x7ffc36947bc0) at ../../../gobject/gsignal.c:3423
        #57 0x00007f60d8760666 in g_signal_emit_valist (instance=0x55a694e8c950, signal_id=32, detail=0, var_args=0x7ffc36947bc0) at ../../../gobject/gsignal.c:3262
    #59 0x00007f60d68b7343 in _gdk_frame_clock_emit_paint (frame_clock=frame_clock@entry=0x55a694e8c950 [GdkFrameClockIdle]) at ../../../gdk/gdkframeclock.c:657
    #60 0x00007f60d68b7b7e in gdk_frame_clock_paint_idle (data=0x55a694e8c950) at ../../../gdk/gdkframeclockidle.c:597
    #61 0x00007f60d68a39db in gdk_threads_dispatch (data=0x55a6949d4570, data@entry=<error reading variable: value has been optimized out>) at ../../../gdk/gdk.c:769
    #62 0x00007f60d830f88e in g_timeout_dispatch (source=0x55a694e1f810, callback=<optimized out>, user_data=<optimized out>) at ../../../glib/gmain.c:5070
    #63 0x00007f60d830c7df in g_main_dispatch (context=context@entry=0x55a694956160) at ../../../glib/gmain.c:3357
    #64 0x00007f60d830ea17 in g_main_context_dispatch_unlocked (context=0x55a694956160) at ../../../glib/gmain.c:4208
    #65 g_main_context_iterate_unlocked (context=0x55a694956160, block=block@entry=1, dispatch=dispatch@entry=1, self=<optimized out>) at ../../../glib/gmain.c:4273
    #66 0x00007f60d830f46f in g_main_loop_run (loop=loop@entry=0x55a694ebf690) at ../../../glib/gmain.c:4475
    #67 0x00007f60d6206c8d in gtk_main () at ../../../gtk/gtkmain.c:1329
    #68 0x00007f60d8b82d85 in wxGUIEventLoop::DoRun (this=0x55a694e24ae0) at ./src/gtk/evtloop.cpp:61
    #69 0x00007f60d84e6021 in wxEventLoopBase::Run (this=0x55a694e24ae0) at ./src/common/evtloopcmn.cpp:87
    #70 0x00007f60d84ac11f in wxAppConsoleBase::MainLoop (this=0x55a6949155b0) at ./src/common/appbase.cpp:381
    #71 0x000055a686447324 in wxt_atexit () at ../../../src/wxterminal/wxt_gui.cpp:4298
    #72 0x000055a6863db79a in gp_exit_cleanup () at ../../../src/stdfn.c:476
    #73 0x000055a686320b05 in main (argc_orig=<optimized out>, argv=0x7ffc36948100) at ../../../src/plot.c:715
    

    So it's hunging somewhere in the pango library.

     
  • Vincent Lefevre

    Vincent Lefevre - 2024-10-30

    I think I have found the cause of the issue: gnuplot does an exit_group(0), which causes a Pango thread to terminate unexpectedly. Now, I don't know whether the is a bug in gnuplot, a bug in the Pango library, or an API breakage.

     
  • Vincent Lefevre

    Vincent Lefevre - 2024-10-30

    If this is an API breakage from Pango, this is also an ABI breakage (as the behavior is modified in an incompatible way). I recall the bug I opened on the Pango side: https://gitlab.gnome.org/GNOME/pango/-/issues/784

     
  • Vincent Lefevre

    Vincent Lefevre - 2024-11-08

    To fix the bug in Gnuplot, pango_cairo_font_map_set_default(NULL); needs to be called before the fork(), as advised in the Pango issue. See my patch for the gnuplot Debian package.

     
    • Ethan Merritt

      Ethan Merritt - 2024-11-08

      I am very impressed by your effort to track this down and happy to apply the fix you found.

      If I understand correctly, this is specifically an issue with cleanup of the main process when it is exiting but (a) the wxt terminal has been used in the session and (b) the -persist option is in effect so that calls into the wxt+cairo+pango libraries may be made by the daughter process that is forked to handle continued display of an open plot window.

      But I may not understand correctly, so maybe you can clarify something:
      Your patch calls pango_cairo_font_map_set_default(NULL) before the fork, so it affects both the parent process and the daughter process. Is this intended and necessary, or is it only the parent process that needs this call? I would have guessed that since the daughter process will continue to use fonts it might be unhappy about this call or at any rate it might suddenly change the font used to display labels etc when the plot window is next refreshed. Is it possible that it would be better to move the call into the else section a few lines down so that only the parent process releases its font maps?

      I.e.

      pid = fork();
      if (!pid) {
          /* daughter process restarts the gui */
          ...
      } else {
          /* main process clean up and exit */
          pango_cairo_font_map_set_default(NULL);
          ...
      }
      

      Or alternatively, if both the parent and daughter process need this before exiting maybe the call should be moved into the separate routine wxt_cleanup()? That way the parent process would release its font maps immediately and the daughter process would eventually release its maps when the window is closed but not immediately.

      For what it's worth, when I compare the valgrind memory tool output before and after applying your fix I can see that the fix prevents eight chunks of "lost" memory associated with fontconfig at program exit. That makes sense. However there are still two chunks of lost memory showing even after applying the fix so maybe further cleanup is possible.

       
  • Vincent Lefevre

    Vincent Lefevre - 2024-11-09

    The advice to release the font map before the fork was from Matthias Clasen (see the Pango bug). In any case, I think that this release should be done at least for the child process. Otherwise, the child process would need the thread attached to the parent process (for the current font map), but this thread is killed when the parent process exits.

     
  • Vincent Lefevre

    Vincent Lefevre - 2024-11-09

    Some additional information... The fork(2) man page says: "The child process is created with a single thread—the one that called fork()." This means that the font-map thread created by Pango remains a thread of the parent process, and there is no duplicate thread for the child process. Said otherwise, the child process will communicate with the font-map thread (via a queue) of the parent process, which is probably fine... until this thread is terminated by the exit() of the parent process. Calling pango_cairo_font_map_set_default(NULL) in the child process ensures that the old font map will not be used; otherwise, the child process may wait for data in the queue, but if the thread has already been terminated, such data will never come, hence the frozen process I'm seeing. Calling pango_cairo_font_map_set_default(NULL) in the parent process (after the fork) is probably useless, except to ensure that memory is released for tools like valgrind. Anyway, calling it before the fork will completely release the associated memory (for both the parent and future child), so that this may be the best thing.

    BTW, the pthread_atfork(3) man page mentions issues with mutexes when using fork(), which is probably the issue that occurs here.

     
    • Ethan Merritt

      Ethan Merritt - 2024-11-09

      Got it. Thanks again for your hard work in tracking this down. Applied to the development branch and will be in the upcoming 6.0.2 release.

       
  • Ethan Merritt

    Ethan Merritt - 2024-11-09
    • status: open --> pending-fixed
    • Group: -->
    • Priority: -->
     
  • Ethan Merritt

    Ethan Merritt - 2024-12-21
    • Status: pending-fixed --> closed-fixed
     

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.