Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Launching window twice causes segfault #48

Open
nathan-b opened this issue May 13, 2023 · 7 comments
Open

Launching window twice causes segfault #48

nathan-b opened this issue May 13, 2023 · 7 comments

Comments

@nathan-b
Copy link

Probably I'm just doing something stupid and wrong, but the documentation didn't tell me how to do what I was trying to do (write a function that displays a window whenever its called). I figured I'd give it a go anyway, and it works brilliantly the first time it's called, but the second time the world falls apart.

Minimal reproduction:

#!/usr/bin/ruby

require 'glimmer-dsl-libui'

include Glimmer

def show_win
  window('foo') {
    button('Boom') {
      on_clicked {
        return "boom"
      }
    }
  }.show
  return "done"
end

puts show_win
puts show_win

I assume return "boom" is the problem here; probably the UI loop needs to clean something up. I tried calling window.destroy before return, but that did not help.

@nathan-b
Copy link
Author

Expected behavior: throw an exception, not segfault.

Extra credit: add an example in the documentation explaining how to do what I'm trying to do, but the correct way (or tell me personally and I'll update the docs).

@AndyObtiva
Copy link
Owner

AndyObtiva commented May 13, 2023

So, the issue is the application is returning from the show_win method in the middle of a GUI application runtime, which interrupts it and breaks it. It is not expected GUI behavior (as per the MVC pattern), and should never really be needed. Any application that does that could be re-written in a way to work correctly without it (for example, by triggering a model method from the listener to do some work as per the proper GUI MVC pattern). If the code was to avoid returning from the method, the application would work just fine, like in this code, which simply puts in the on_clicked listener instead of outside the show_win method (though it could call a method on a model if there is a need to do something more sophisticated):

#!/usr/bin/ruby

require 'glimmer-dsl-libui'

include Glimmer

def show_win
  window('foo') {
    button('Boom') {
      on_clicked {
        puts "boom"
      }
    }
  }.show
end

show_win
show_win

Returning from a method is not a proper way for exiting a GUI application. The proper way is for the user to click the x button in the window, or for the application to destroy the window and then quit LibUI's event loop like in the following code:

#!/usr/bin/ruby

require 'glimmer-dsl-libui'

include Glimmer

def show_win
  window('foo') { |w|
    button('Boom') {
      on_clicked {
        puts "boom"
        w.destroy
        ::LibUI.quit
      }
    }
  }.show
end

show_win
show_win

That should accomplish what you want as every time you click the Boom button, it destroys the window and quits LibUI's event loop, thus allowing the next invocation of show_win to relaunch a new window from scratch.

This behavior is partially documented under: https://github.com/AndyObtiva/glimmer-dsl-libui#smart-defaults-and-conventions

I just added extra documentation about it under Common Control Operations:
https://github.com/AndyObtiva/glimmer-dsl-libui/tree/master#common-control-operations

### Common Control Operations
- `destroy` (note that for closing a `window`, in addition to calling `somewindow.destroy`, you also have to call `::LibUI.quit`)
...

If you feel there is a need to provide further clarification (like for a use-case I am not aware of), or even an example as you suggested, please feel free to ask or just share a Pull Request!

Please confirm to me that the examples above resolve your issue in order for me to close this issue (or feel free to ask other questions if needed).

@AndyObtiva
Copy link
Owner

AndyObtiva commented May 14, 2023

Interesting!

Apparently, if you really insist on the method return approach, this code makes it work (on the Mac at least, where I ran it):

#!/usr/bin/ruby

require 'glimmer-dsl-libui'

include Glimmer

def show_win
  window('foo') { |w|
    button('Boom') {
      on_clicked {
        w.destroy
        ::LibUI.quit
        return "boom"
      }
    }
  }.show
  return "done"
end

puts show_win
puts show_win

I don't recommend it, but if you really want to return from the method (instead of calling a model method as per MVC), the code above works.

I am of the belief that there are always exceptions to the rule, and it is good to bend rules to be pragmatic every once in a while (staying mindful of the pros/cons vs other approaches). So, perhaps in a quick and dirty Ruby scripting situation, this method return approach might be helpful. But, I'd still think twice before applying it.

@AndyObtiva
Copy link
Owner

AndyObtiva commented May 14, 2023

One more thing. When running the original application, I don't get a seg fault on the Mac. I get an unexpected return (LocalJumpError) error:

tmp/test_open_window_multiple_times0.rb:13:in `block (3 levels) in show_win': unexpected return (LocalJumpError)
	from /Users/andymaleh/code/glimmer-dsl-libui/lib/glimmer/libui/control_proxy.rb:171:in `block in handle_listener'
	from /Users/andymaleh/code/glimmer-dsl-libui/lib/glimmer/libui/control_proxy.rb:175:in `block (2 levels) in handle_listener'
	from /Users/andymaleh/code/glimmer-dsl-libui/lib/glimmer/libui/control_proxy.rb:175:in `map'
	from /Users/andymaleh/code/glimmer-dsl-libui/lib/glimmer/libui/control_proxy.rb:175:in `block in handle_listener'
	from /Users/andymaleh/.rvm/rubies/ruby-3.1.0/lib/ruby/3.1.0/fiddle/closure.rb:45:in `call'
	from /Users/andymaleh/.rvm/gems/ruby-3.1.0@glimmer-dsl-libui/gems/libui-0.1.2.pre-arm64-darwin/lib/libui/ffi.rb:20:in `call'
	from /Users/andymaleh/.rvm/gems/ruby-3.1.0@glimmer-dsl-libui/gems/libui-0.1.2.pre-arm64-darwin/lib/libui/ffi.rb:20:in `uiMain'
	from /Users/andymaleh/.rvm/gems/ruby-3.1.0@glimmer-dsl-libui/gems/libui-0.1.2.pre-arm64-darwin/lib/libui/libui_base.rb:46:in `public_send'
	from /Users/andymaleh/.rvm/gems/ruby-3.1.0@glimmer-dsl-libui/gems/libui-0.1.2.pre-arm64-darwin/lib/libui/libui_base.rb:46:in `block (2 levels) in <module:LibUIBase>'
	from /Users/andymaleh/code/glimmer-dsl-libui/lib/glimmer/libui/control_proxy/window_proxy.rb:69:in `show'
	from tmp/test_open_window_multiple_times0.rb:16:in `show_win'
	from tmp/test_open_window_multiple_times0.rb:21:in `<main>'

That is totally correct as it is mentioning that returning is unexpected behavior.

If you are still encountering this somehow, it might be an issue on a specific environment. I know that Linux does not support launching a window multiple times within the same Ruby session unfortunately. It is a known problem observed when using girb (Glimmer IRB) on Linux, which requires exiting and restarting on every window launch (reported here: #45).

Please confirm what environment you are on (Linux, Windows, or Mac) if the code snippets above do not resolve your issue.

@AndyObtiva
Copy link
Owner

AndyObtiva commented May 14, 2023

OK, I just tested your application in Linux and confirmed that in Linux, unlike on the Mac, I don't get an unexpected return (LocalJumpError) exception, yet a segfault. Additionally, as mentioned in my last comment, the Linux implementation of the underlying C libui library does not currently support launching a window multiple times in the same Ruby session.

I am reporting this issue to the upstream projects (libui C library and bindings). I will report back here once it is fixed.

@nathan-b
Copy link
Author

nathan-b commented May 14, 2023

Thanks so much for the information and for reporting the issue upstream!

The problem I'm (clumsily) trying to solve is that I need a dialog box asking for input from the user. I need this at several points in the application, so I wrote a function that creates said dialog box. Once the user presses "OK" or "Cancel" I want the function to return the user input (or nil if the user pressed Cancel). Hopefully this explains why I'm trying to do such a seemingly silly thing :)

I actually tried something similar to your approach (calling destroy on the window and quit on LibUI), but I guess at that point I ran afoul of the upstream bug you mentioned. I'll watch this issue and try again once it gets fixed and merged!

@AndyObtiva
Copy link
Owner

AndyObtiva commented May 14, 2023

You're welcome.

I reported your issue at the underlying C libui project:
libui-ng/libui-ng#205

And, the libui binding project:
kojix2/LibUI#45

In the meantime, here is a workaround.

You can write multiple Ruby applications that call each other conditionally based on your specific needs' logic. That way, instead of attempting to relaunch a window in the same Ruby application, you simply call another Ruby application to launch a window in a separate Ruby session (using system, ticks, or IO.popen).

I actually used this approach (using IO.popen) in building the Glimmer Meta-Example, which enables launching many Glimmer DSL for LibUI windows at the same time side by side from the same Ruby application session:

meta-example

You can go ahead and verify that for yourself by launching it via these instructions (ruby -r './lib/glimmer-dsl-libui' examples/meta_example.rb if you have the glimmer-dsl-libui project cloned locally):
https://github.com/AndyObtiva/glimmer-dsl-libui#examples

I hope this will address your needs until the reported issue is fixed.

Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants