From 887a17f5be4b8e3ba1c86d2c03d750dcfd9d5e31 Mon Sep 17 00:00:00 2001 From: Ian Date: Mon, 17 Jan 2022 17:12:41 -0500 Subject: [PATCH 01/11] add wait_eventloop_stopping() --- src/Gtk.jl | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Gtk.jl b/src/Gtk.jl index c0340da9..22a2ea02 100644 --- a/src/Gtk.jl +++ b/src/Gtk.jl @@ -185,7 +185,7 @@ Set whether Gtk's event loop is running. """ function enable_eventloop(b::Bool = true; wait_stopped::Bool = false) lock(enable_eventloop_lock) do # handle widgets that are being shown/destroyed from different threads - isassigned(quit_task) && wait(quit_task[]) # prevents starting while the async is still stopping + wait_eventloop_stopping() # prevents starting while the async is still stopping if b if !is_eventloop_running() global gtk_main_task = schedule(Task(gtk_main)) @@ -230,6 +230,16 @@ Check whether Gtk's event loop is running. """ is_eventloop_running() = gtk_main_running[] +""" + Gtk.wait_eventloop_stopping() + +If the eventloop is stopping, wait. +""" +function wait_eventloop_stopping() + isassigned(quit_task) && wait(quit_task[]) + return +end + const ser_version = Serialization.ser_version let cachedir = joinpath(splitdir(@__FILE__)[1], "..", "gen") fastgtkcache = joinpath(cachedir, "gtk$(libgtk_version.major)_julia_ser$(ser_version)") From 75c6d6f32788c80af475319edcde01d54b5a9e63 Mon Sep 17 00:00:00 2001 From: Ian Date: Mon, 17 Jan 2022 18:55:12 -0500 Subject: [PATCH 02/11] fix and expand tests --- test/misc.jl | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/test/misc.jl b/test/misc.jl index 347fed78..42eecf1f 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -38,6 +38,13 @@ destroy(win) @test isa(Gtk.GdkEventKey(), Gtk.GdkEventKey) +# make sure all shown widgets have been destroyed, otherwise the eventloop +# won't stop automatically +for (w,_) in Gtk.shown_widgets + destroy(w) +end +Gtk.wait_eventloop_stopping() + @testset "Eventloop control" begin before = Gtk.auto_idle[] @@ -60,6 +67,56 @@ destroy(win) @test !Gtk.is_eventloop_running() end @test Gtk.is_eventloop_running() + Gtk.enable_eventloop(false, wait_stopped = true) + @test !Gtk.is_eventloop_running() + + @testset "pause_eventloop: multithreaded code doesn't block" begin + Gtk.auto_idle[] = true + Threads.nthreads() < 1 && @warn "Threads.nthreads() == 1. Multithread blocking tests are not effective" + + function multifoo() + Threads.@threads for _ in 1:Threads.nthreads() + sleep(0.1) + end + end + + @test !Gtk.is_eventloop_running() + win = Gtk.Window("Multithread test", 400, 300) + showall(win) + @test Gtk.is_eventloop_running() + for i in 1:10 + Gtk.pause_eventloop() do + @test !Gtk.is_eventloop_running() + t = @elapsed multifoo() # should take slightly more than 0.1 seconds + @test t < 4.5 # given the Glib uv_prepare timeout is 5000 ms + end + end + @test Gtk.is_eventloop_running() + destroy(win) + Gtk.wait_eventloop_stopping() + end + + @testset "wait_eventloop_stopping: waits for stop" begin + c = GtkCanvas() + win = GtkWindow(c) + showall(win) + @test Gtk.is_eventloop_running() + destroy(win) + Gtk.wait_eventloop_stopping() + @test !Gtk.is_eventloop_running() + end + + @testset "pause_eventloop: doesn't restart a stopping eventloop" begin + c = GtkCanvas() + win = GtkWindow(c) + showall(win) + @test Gtk.is_eventloop_running() + destroy(win) + Gtk.pause_eventloop() do + @test !Gtk.is_eventloop_running() + end + @test !Gtk.is_eventloop_running() + end Gtk.auto_idle[] = before end From 21cdea827c5673cfe61d234ea584d82d907b5656 Mon Sep 17 00:00:00 2001 From: Ian Date: Mon, 17 Jan 2022 19:15:53 -0500 Subject: [PATCH 03/11] enable 2 threads for multithread tests --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0292000e..b0aa5740 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,8 @@ jobs: - uses: julia-actions/julia-runtest@v1 with: prefix: ${{ matrix.prefix }} + env: + JULIA_NUM_THREADS: 2 - uses: julia-actions/julia-uploadcodecov@v0.1 continue-on-error: true - uses: julia-actions/julia-uploadcoveralls@v0.1 From 1990cbe40bed2f0b48c1ee9420ce46cb0da56e53 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 17 Jan 2022 21:42:31 -0500 Subject: [PATCH 04/11] make yield non platform-specific --- src/base.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base.jl b/src/base.jl index 4bc5d603..94603c0a 100644 --- a/src/base.jl +++ b/src/base.jl @@ -39,7 +39,7 @@ function handle_auto_idle(w::GtkWidget) isempty(shown_widgets) && enable_eventloop(false) end end - @static Sys.iswindows() && yield() # issue #610 + yield() # issue #610 end end function show(w::GtkWidget) From cae10a40a5537693da88ae50208d6e29f87c1e51 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 17 Jan 2022 21:58:45 -0500 Subject: [PATCH 05/11] close orphaned windows from other tests --- test/glist.jl | 9 ++++++--- test/gui.jl | 1 + test/misc.jl | 5 +---- test/tree.jl | 4 +++- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/test/glist.jl b/test/glist.jl index f2881a16..50db5edc 100644 --- a/test/glist.jl +++ b/test/glist.jl @@ -7,10 +7,10 @@ using Test @testset "pointers" begin -w = Window("Window", 400, 300) +w1 = Window("Window", 400, 300) nb = Notebook() -w = push!(Window("Notebook"),nb) -l = ccall((:gtk_container_get_children,Gtk.libgtk),Ptr{Gtk._GList{Gtk.GtkWidget}},(Ptr{Gtk.GObject},),w) +w2 = push!(Window("Notebook"),nb) +l = ccall((:gtk_container_get_children,Gtk.libgtk),Ptr{Gtk._GList{Gtk.GtkWidget}},(Ptr{Gtk.GObject},),w2) @test eltype(l)==Gtk.GtkWidget @@ -22,6 +22,9 @@ for item in l @test item==nb end +destroy(w1) +destroy(w2) + end @testset "string" begin diff --git a/test/gui.jl b/test/gui.jl index 786aa038..10834b15 100755 --- a/test/gui.jl +++ b/test/gui.jl @@ -582,6 +582,7 @@ end @test mtrx.xx == 300 @test mtrx.yy == 280 @test mtrx.xy == mtrx.yx == mtrx.x0 == mtrx.y0 == 0 + destroy(win) end @testset "Menus" begin diff --git a/test/misc.jl b/test/misc.jl index 42eecf1f..cfc1efad 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -40,10 +40,7 @@ destroy(win) # make sure all shown widgets have been destroyed, otherwise the eventloop # won't stop automatically -for (w,_) in Gtk.shown_widgets - destroy(w) -end -Gtk.wait_eventloop_stopping() +@test length(Gtk.shown_widgets) == 0 @testset "Eventloop control" begin before = Gtk.auto_idle[] diff --git a/test/tree.jl b/test/tree.jl index 074ccde0..9613e58e 100644 --- a/test/tree.jl +++ b/test/tree.jl @@ -76,4 +76,6 @@ select!(selection, iter) @test length(selection) == 1 # this crashes -# iters = Gtk.selected_rows(selection) \ No newline at end of file +# iters = Gtk.selected_rows(selection) + +destroy(window) From 82f1e0a725ad8586e04c1f93f3143e38e90a49d7 Mon Sep 17 00:00:00 2001 From: Ian Date: Mon, 17 Jan 2022 20:21:01 -0500 Subject: [PATCH 06/11] remove `@async` for eventloop quit --- src/Gtk.jl | 26 ++++---------------------- test/misc.jl | 6 ++---- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/src/Gtk.jl b/src/Gtk.jl index 22a2ea02..a5cc8141 100644 --- a/src/Gtk.jl +++ b/src/Gtk.jl @@ -176,16 +176,14 @@ end const auto_idle = Ref{Bool}(true) # control default via ENV["GTK_AUTO_IDLE"] const gtk_main_running = Ref{Bool}(false) -const quit_task = Ref{Task}() const enable_eventloop_lock = Base.ReentrantLock() """ Gtk.enable_eventloop(b::Bool = true) Set whether Gtk's event loop is running. """ -function enable_eventloop(b::Bool = true; wait_stopped::Bool = false) +function enable_eventloop(b::Bool = true) lock(enable_eventloop_lock) do # handle widgets that are being shown/destroyed from different threads - wait_eventloop_stopping() # prevents starting while the async is still stopping if b if !is_eventloop_running() global gtk_main_task = schedule(Task(gtk_main)) @@ -193,14 +191,8 @@ function enable_eventloop(b::Bool = true; wait_stopped::Bool = false) end else if is_eventloop_running() - # @async and short sleep is needer on MacOS at least, otherwise - # the window doesn't always finish closing before the eventloop stops. - quit_task[] = @async begin - sleep(0.2) - gtk_quit() - gtk_main_running[] = false - end - wait_stopped && wait(quit_task[]) + gtk_quit() + gtk_main_running[] = false end end end @@ -215,7 +207,7 @@ eventloop, unless `force = true`. """ function pause_eventloop(f; force = false) was_running = is_eventloop_running() - (force || auto_idle[]) && enable_eventloop(false, wait_stopped = true) + (force || auto_idle[]) && enable_eventloop(false) try f() finally @@ -230,16 +222,6 @@ Check whether Gtk's event loop is running. """ is_eventloop_running() = gtk_main_running[] -""" - Gtk.wait_eventloop_stopping() - -If the eventloop is stopping, wait. -""" -function wait_eventloop_stopping() - isassigned(quit_task) && wait(quit_task[]) - return -end - const ser_version = Serialization.ser_version let cachedir = joinpath(splitdir(@__FILE__)[1], "..", "gen") fastgtkcache = joinpath(cachedir, "gtk$(libgtk_version.major)_julia_ser$(ser_version)") diff --git a/test/misc.jl b/test/misc.jl index cfc1efad..26ec3d54 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -64,7 +64,7 @@ destroy(win) @test !Gtk.is_eventloop_running() end @test Gtk.is_eventloop_running() - Gtk.enable_eventloop(false, wait_stopped = true) + Gtk.enable_eventloop(false) @test !Gtk.is_eventloop_running() @testset "pause_eventloop: multithreaded code doesn't block" begin @@ -90,16 +90,14 @@ destroy(win) end @test Gtk.is_eventloop_running() destroy(win) - Gtk.wait_eventloop_stopping() end - @testset "wait_eventloop_stopping: waits for stop" begin + @testset "eventloop is stopped immediately after a destroy(win) completes" begin c = GtkCanvas() win = GtkWindow(c) showall(win) @test Gtk.is_eventloop_running() destroy(win) - Gtk.wait_eventloop_stopping() @test !Gtk.is_eventloop_running() end From 5e0c74944ca0d64d80b5c058a20fbd9bd9c44b3a Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 18 Jan 2022 17:28:31 -0500 Subject: [PATCH 07/11] make stopping the eventloop more robust --- src/Gtk.jl | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Gtk.jl b/src/Gtk.jl index a5cc8141..1c9b6a84 100644 --- a/src/Gtk.jl +++ b/src/Gtk.jl @@ -159,15 +159,6 @@ function __init__() C_NULL, C_NULL, "Julia Gtk Bindings", C_NULL, C_NULL, error_check) end - # if g_main_depth > 0, a glib main-loop is already running. - # unfortunately this call does not reliably reflect the state after the - # loop has been stopped or restarted, so only use it once at the start - gtk_main_running[] = ccall((:g_main_depth, GLib.libglib), Cint, ()) > 0 - - # Given GLib provides `g_idle_add` to specify what happens during idle, this allows - # that call to also start the eventloop - GLib.gtk_eventloop_f[] = enable_eventloop - auto_idle[] = get(ENV, "GTK_AUTO_IDLE", "true") == "true" # by default, defer starting the event loop until either `show`, `showall`, or `g_idle_add` is called @@ -175,8 +166,8 @@ function __init__() end const auto_idle = Ref{Bool}(true) # control default via ENV["GTK_AUTO_IDLE"] -const gtk_main_running = Ref{Bool}(false) const enable_eventloop_lock = Base.ReentrantLock() + """ Gtk.enable_eventloop(b::Bool = true) @@ -187,16 +178,24 @@ function enable_eventloop(b::Bool = true) if b if !is_eventloop_running() global gtk_main_task = schedule(Task(gtk_main)) - gtk_main_running[] = true end else if is_eventloop_running() - gtk_quit() - gtk_main_running[] = false + recursive_quit_main() end end end end +main_depth() = ccall((:g_main_depth, Gtk.GLib.libglib), Cint, ()) + +function recursive_quit_main() + gtk_quit() + if main_depth() > 1 + @idle_add begin + recursive_quit_main() + end + end +end """ Gtk.pause_eventloop(f; force = false) @@ -211,7 +210,7 @@ function pause_eventloop(f; force = false) try f() finally - (force || auto_idle[]) && enable_eventloop(was_running) + (force || auto_idle[]) && was_running && enable_eventloop() end end @@ -220,7 +219,7 @@ end Check whether Gtk's event loop is running. """ -is_eventloop_running() = gtk_main_running[] +is_eventloop_running() = main_depth() > 0 const ser_version = Serialization.ser_version let cachedir = joinpath(splitdir(@__FILE__)[1], "..", "gen") From f3e3136de14cbf0c7b8ccd257251d8af3dd4f75e Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 18 Jan 2022 17:29:20 -0500 Subject: [PATCH 08/11] remove eventloop starter from `@idle_add` --- src/GLib/GLib.jl | 2 -- src/GLib/signals.jl | 1 - test/glib.jl | 4 ++++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/GLib/GLib.jl b/src/GLib/GLib.jl index b70a9cb3..c9b2ad9f 100644 --- a/src/GLib/GLib.jl +++ b/src/GLib/GLib.jl @@ -41,8 +41,6 @@ cfunction_(@nospecialize(f), r, a::Tuple) = cfunction_(f, r, Tuple{a...}) end end -const gtk_eventloop_f = Ref{Function}() - # local function, handles Symbol and makes UTF8-strings easier const AbstractStringLike = Union{AbstractString, Symbol} bytestring(s) = String(s) diff --git a/src/GLib/signals.jl b/src/GLib/signals.jl index a917048a..075a7679 100644 --- a/src/GLib/signals.jl +++ b/src/GLib/signals.jl @@ -381,7 +381,6 @@ end @deprecate g_timeout_add(interval, cb, user_data) g_timeout_add(() -> cb(user_data), interval) function g_idle_add(cb::Function) - gtk_eventloop_f[](true) callback = @cfunction(_g_callback, Cint, (Ref{Function},)) ref, deref = gc_ref_closure(cb) return ccall((:g_idle_add_full , libglib),Cint, diff --git a/test/glib.jl b/test/glib.jl index 39cd240a..248dbeeb 100644 --- a/test/glib.jl +++ b/test/glib.jl @@ -24,6 +24,8 @@ repr = Base.print_to_string(wrap) #should display properties x = Ref{Int}(1) +Gtk.enable_eventloop(true) + function g_timeout_add_cb() x[] = 2 false @@ -82,6 +84,8 @@ g_timeout_add(()->g_timeout_add_cb(x), 1) sleep(0.5) @test x[] == 2 +Gtk.enable_eventloop(false) + end # TODO From 5d459da5c1d40a10aed59f3e80ce5f9a01f0d83c Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 18 Jan 2022 18:59:45 -0500 Subject: [PATCH 09/11] soft-deprecate gtk_quit -> gtk_main_quit --- example/gl-area.jl | 2 +- src/Gtk.jl | 2 +- src/events.jl | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/example/gl-area.jl b/example/gl-area.jl index 75e229cc..3215ab05 100644 --- a/example/gl-area.jl +++ b/example/gl-area.jl @@ -44,6 +44,6 @@ showall(win) # https://stackoverflow.com/a/33571506/1500988 signal_connect(win, :destroy) do widget - Gtk.gtk_quit() + Gtk.gtk_main_quit() end Gtk.gtk_main() diff --git a/src/Gtk.jl b/src/Gtk.jl index 1c9b6a84..ce72b393 100644 --- a/src/Gtk.jl +++ b/src/Gtk.jl @@ -189,7 +189,7 @@ end main_depth() = ccall((:g_main_depth, Gtk.GLib.libglib), Cint, ()) function recursive_quit_main() - gtk_quit() + gtk_main_quit() if main_depth() > 1 @idle_add begin recursive_quit_main() diff --git a/src/events.jl b/src/events.jl index da9bd0e0..7a897f7e 100644 --- a/src/events.jl +++ b/src/events.jl @@ -2,10 +2,12 @@ gtk_main() = GLib.g_sigatom() do ccall((:gtk_main, libgtk), Nothing, ()) end -function gtk_quit() +function gtk_main_quit() ccall((:gtk_main_quit, libgtk), Nothing, ()) end +const gtk_quit = gtk_main_quit # deprecated + add_events(widget::GtkWidget, mask::Integer) = ccall((:gtk_widget_add_events, libgtk), Nothing, (Ptr{GObject}, GEnum), widget, mask) # widget[:event] = function(ptr, obj) From 7b953a5ac164dd97d78ce927265636a185e11c7f Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 17 Jan 2022 22:11:48 -0500 Subject: [PATCH 10/11] sequencing tweaks --- src/GLib/GLib.jl | 2 ++ src/Gtk.jl | 47 +++++++++++++++++++++++++++++++++++------------ src/base.jl | 6 +++--- 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/GLib/GLib.jl b/src/GLib/GLib.jl index c9b2ad9f..6a957f0c 100644 --- a/src/GLib/GLib.jl +++ b/src/GLib/GLib.jl @@ -51,6 +51,8 @@ bytestring(s::Ptr{UInt8}) = unsafe_string(s) g_malloc(s::Integer) = ccall((:g_malloc, libglib), Ptr{Nothing}, (Csize_t,), s) g_free(p::Ptr) = ccall((:g_free, libglib), Nothing, (Ptr{Nothing},), p) +main_depth() = ccall((:g_main_depth, libglib), Cint, ()) + ccall((:g_type_init, libgobject), Nothing, ()) include("MutableTypes.jl") diff --git a/src/Gtk.jl b/src/Gtk.jl index ce72b393..dc7f39b9 100644 --- a/src/Gtk.jl +++ b/src/Gtk.jl @@ -160,40 +160,62 @@ function __init__() end auto_idle[] = get(ENV, "GTK_AUTO_IDLE", "true") == "true" - - # by default, defer starting the event loop until either `show`, `showall`, or `g_idle_add` is called - enable_eventloop(!auto_idle[]) + # by default, defer starting the event loop until widgets are "realized" + if auto_idle[] + # Start-stopping the event loop once makes the auto-stop process + # more stable. Reason unknown + enable_eventloop(true) + enable_eventloop(false) + else + enable_eventloop(true) + end end const auto_idle = Ref{Bool}(true) # control default via ENV["GTK_AUTO_IDLE"] const enable_eventloop_lock = Base.ReentrantLock() +const eventloop_instructed_to_stop = Ref{Bool}(false) """ Gtk.enable_eventloop(b::Bool = true) Set whether Gtk's event loop is running. """ -function enable_eventloop(b::Bool = true) +function enable_eventloop(b::Bool = true; wait = true) lock(enable_eventloop_lock) do # handle widgets that are being shown/destroyed from different threads if b if !is_eventloop_running() + eventloop_instructed_to_stop[] = false global gtk_main_task = schedule(Task(gtk_main)) + if !is_eventloop_running() && wait + t = Timer(5) # TODO: replace with Base.timedwait when 1.3 is dropped + while isopen(t) && !is_eventloop_running() + sleep(0.1) + end + isopen(t) || @debug "enable_eventloop: timed-out waiting for eventloop to start" + end end else if is_eventloop_running() recursive_quit_main() + eventloop_instructed_to_stop[] = true + if is_eventloop_running() && wait + t = Timer(5) # TODO: replace with Base.timedwait when 1.3 is dropped + while isopen(t) && is_eventloop_running() + sleep(0.1) + end + isopen(t) || @debug "enable_eventloop: timed-out waiting for eventloop to stop" + end end end + return is_eventloop_running() end end -main_depth() = ccall((:g_main_depth, Gtk.GLib.libglib), Cint, ()) +# based on https://stackoverflow.com/a/44292631 function recursive_quit_main() gtk_main_quit() - if main_depth() > 1 - @idle_add begin - recursive_quit_main() - end + if GLib.main_depth() > 1 + @idle_add recursive_quit_main() end end @@ -205,12 +227,12 @@ pausing. Respects whether Gtk.jl is configured to allow auto-stopping of the eventloop, unless `force = true`. """ function pause_eventloop(f; force = false) - was_running = is_eventloop_running() + was_running = eventloop_instructed_to_stop[] ? false : is_eventloop_running() (force || auto_idle[]) && enable_eventloop(false) try f() finally - (force || auto_idle[]) && was_running && enable_eventloop() + (force || auto_idle[]) && enable_eventloop(was_running) end end @@ -219,7 +241,8 @@ end Check whether Gtk's event loop is running. """ -is_eventloop_running() = main_depth() > 0 +is_eventloop_running() = GLib.main_depth() > 0 + const ser_version = Serialization.ser_version let cachedir = joinpath(splitdir(@__FILE__)[1], "..", "gen") diff --git a/src/base.jl b/src/base.jl index 94603c0a..2d7fbb2c 100644 --- a/src/base.jl +++ b/src/base.jl @@ -32,11 +32,11 @@ const shown_widgets = WeakKeyDict() function handle_auto_idle(w::GtkWidget) if auto_idle[] signal_connect(w, :realize) do w - enable_eventloop(true) + enable_eventloop(true, wait = false) # can't wait in a callback, unfortunately shown_widgets[w] = nothing - signal_connect(w, :destroy, #= after =# true) do w + signal_connect(w, :destroy, #= after =# false) do w delete!(shown_widgets, w) - isempty(shown_widgets) && enable_eventloop(false) + isempty(shown_widgets) && enable_eventloop(false, wait = false) end end yield() # issue #610 From f7e0109215f3ec8129a7d7cfef29b37d5c12aa67 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 18 Jan 2022 19:15:20 -0500 Subject: [PATCH 11/11] test reorg --- test/eventloop.jl | 105 ++++++++++++++++++++++++++++++++++++++++++++++ test/misc.jl | 78 ---------------------------------- test/runtests.jl | 1 + 3 files changed, 106 insertions(+), 78 deletions(-) create mode 100644 test/eventloop.jl diff --git a/test/eventloop.jl b/test/eventloop.jl new file mode 100644 index 00000000..47ed23bc --- /dev/null +++ b/test/eventloop.jl @@ -0,0 +1,105 @@ +@testset "eventloop" begin + # make sure all shown widgets have been destroyed, otherwise the eventloop + # won't stop automatically + @test length(Gtk.shown_widgets) == 0 + + @testset "control" begin + before = Gtk.auto_idle[] + + @testset "basics" begin + Gtk.auto_idle[] = true + Gtk.enable_eventloop(false) + @test !Gtk.is_eventloop_running() + Gtk.enable_eventloop(true) + @test Gtk.is_eventloop_running() + Gtk.enable_eventloop(false) + @test !Gtk.is_eventloop_running() + end + + @testset "pause_eventloop" begin + + @testset "pauses then restarts" begin + Gtk.enable_eventloop(true) + @test Gtk.is_eventloop_running() + Gtk.pause_eventloop() do + @test !Gtk.is_eventloop_running() + end + @test Gtk.is_eventloop_running() + end + + @testset "doesn't restart a stopping eventloop" begin + Gtk.enable_eventloop(false) + c = GtkCanvas() + win = GtkWindow(c) + showall(win) + sleep(1) + @test Gtk.is_eventloop_running() + destroy(win) + # the eventloop is likely still stopping here + Gtk.pause_eventloop() do + @test !Gtk.is_eventloop_running() + end + @test !Gtk.is_eventloop_running() + end + + @testset "observes auto_idle = false" begin + Gtk.auto_idle[] = false + Gtk.enable_eventloop(true) + Gtk.pause_eventloop() do + @test Gtk.is_eventloop_running() + end + @test Gtk.is_eventloop_running() + end + + @testset "observes force = true" begin + Gtk.auto_idle[] = false + Gtk.enable_eventloop(true) + Gtk.pause_eventloop(force = true) do + @test !Gtk.is_eventloop_running() + end + @test Gtk.is_eventloop_running() + end + + # Note: Test disabled because this isn't true. The event loop takes some time to stop. + # TODO: Figure out how to wait in the handle_auto_idle callbacks + + # @testset "eventloop is stopped immediately after a destroy(win) completes" begin + # c = GtkCanvas() + # win = GtkWindow(c) + # showall(win) + # @test Gtk.is_eventloop_running() + # destroy(win) + # @test !Gtk.is_eventloop_running() + # end + end + + Gtk.auto_idle[] = before + end + + @testset "Multithreading" begin + @testset "no blocking when eventloop is paused" begin + Gtk.auto_idle[] = true + Threads.nthreads() < 1 && @warn "Threads.nthreads() == 1. Multithread blocking tests are not effective" + + function multifoo() + Threads.@threads for _ in 1:Threads.nthreads() + sleep(0.1) + end + end + + Gtk.enable_eventloop(false) + win = Gtk.Window("Multithread test", 400, 300) + showall(win) + @test Gtk.is_eventloop_running() + for i in 1:10 + Gtk.pause_eventloop() do + @test !Gtk.is_eventloop_running() + t = @elapsed multifoo() # should take slightly more than 0.1 seconds + @test t < 4.5 # given the Glib uv_prepare timeout is 5000 ms + end + end + @test Gtk.is_eventloop_running() + destroy(win) + end + end +end \ No newline at end of file diff --git a/test/misc.jl b/test/misc.jl index 26ec3d54..b307ffa5 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -38,82 +38,4 @@ destroy(win) @test isa(Gtk.GdkEventKey(), Gtk.GdkEventKey) -# make sure all shown widgets have been destroyed, otherwise the eventloop -# won't stop automatically -@test length(Gtk.shown_widgets) == 0 - -@testset "Eventloop control" begin - before = Gtk.auto_idle[] - - Gtk.enable_eventloop(true) - @test Gtk.is_eventloop_running() - - Gtk.auto_idle[] = true - Gtk.pause_eventloop() do - @test !Gtk.is_eventloop_running() - end - @test Gtk.is_eventloop_running() - - Gtk.auto_idle[] = false - Gtk.pause_eventloop() do - @test Gtk.is_eventloop_running() - end - @test Gtk.is_eventloop_running() - - Gtk.pause_eventloop(force = true) do - @test !Gtk.is_eventloop_running() - end - @test Gtk.is_eventloop_running() - Gtk.enable_eventloop(false) - @test !Gtk.is_eventloop_running() - - @testset "pause_eventloop: multithreaded code doesn't block" begin - Gtk.auto_idle[] = true - Threads.nthreads() < 1 && @warn "Threads.nthreads() == 1. Multithread blocking tests are not effective" - - function multifoo() - Threads.@threads for _ in 1:Threads.nthreads() - sleep(0.1) - end - end - - @test !Gtk.is_eventloop_running() - win = Gtk.Window("Multithread test", 400, 300) - showall(win) - @test Gtk.is_eventloop_running() - for i in 1:10 - Gtk.pause_eventloop() do - @test !Gtk.is_eventloop_running() - t = @elapsed multifoo() # should take slightly more than 0.1 seconds - @test t < 4.5 # given the Glib uv_prepare timeout is 5000 ms - end - end - @test Gtk.is_eventloop_running() - destroy(win) - end - - @testset "eventloop is stopped immediately after a destroy(win) completes" begin - c = GtkCanvas() - win = GtkWindow(c) - showall(win) - @test Gtk.is_eventloop_running() - destroy(win) - @test !Gtk.is_eventloop_running() - end - - @testset "pause_eventloop: doesn't restart a stopping eventloop" begin - c = GtkCanvas() - win = GtkWindow(c) - showall(win) - @test Gtk.is_eventloop_running() - destroy(win) - Gtk.pause_eventloop() do - @test !Gtk.is_eventloop_running() - end - @test !Gtk.is_eventloop_running() - end - - Gtk.auto_idle[] = before -end - end diff --git a/test/runtests.jl b/test/runtests.jl index 5316cce8..6d128b3d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,5 +7,6 @@ include("gui.jl") include("list.jl") include("misc.jl") include("text.jl") +include("eventloop.jl") end