1 | #include <glib.h> |
2 | #ifdef G_OS_UNIX |
3 | #include <unistd.h> |
4 | #endif |
5 | |
6 | static GMainLoop *loop; |
7 | |
8 | static gboolean |
9 | stop_waiting (gpointer data) |
10 | { |
11 | g_main_loop_quit (loop); |
12 | |
13 | return G_SOURCE_REMOVE; |
14 | } |
15 | |
16 | static gboolean |
17 | unreachable_callback (gpointer data) |
18 | { |
19 | g_assert_not_reached (); |
20 | |
21 | return G_SOURCE_REMOVE; |
22 | } |
23 | |
24 | static void |
25 | test_seconds (void) |
26 | { |
27 | guint id; |
28 | |
29 | /* Bug 642052 mentions that g_timeout_add_seconds(21475) schedules a |
30 | * job that runs once per second. |
31 | * |
32 | * Test that that isn't true anymore by scheduling two jobs: |
33 | * - one, as above |
34 | * - another that runs in 2100ms |
35 | * |
36 | * If everything is working properly, the 2100ms one should run first |
37 | * (and exit the mainloop). If we ever see the 21475 second job run |
38 | * then we have trouble (since it ran in less than 2 seconds). |
39 | * |
40 | * We need a timeout of at least 2 seconds because |
41 | * g_timeout_add_seconds() can add as much as an additional second of |
42 | * latency. |
43 | */ |
44 | g_test_bug (bug_uri_snippet: "https://bugzilla.gnome.org/show_bug.cgi?id=642052" ); |
45 | loop = g_main_loop_new (NULL, FALSE); |
46 | |
47 | g_timeout_add (interval: 2100, function: stop_waiting, NULL); |
48 | id = g_timeout_add_seconds (interval: 21475, function: unreachable_callback, NULL); |
49 | |
50 | g_main_loop_run (loop); |
51 | g_main_loop_unref (loop); |
52 | |
53 | g_source_remove (tag: id); |
54 | } |
55 | |
56 | static void |
57 | test_weeks_overflow (void) |
58 | { |
59 | guint id; |
60 | guint interval_seconds; |
61 | |
62 | /* Internally, the guint interval (in seconds) was converted to milliseconds |
63 | * then stored in a guint variable. This meant that any interval larger than |
64 | * G_MAXUINT / 1000 would overflow. |
65 | * |
66 | * On a system with 32-bit guint, the interval (G_MAXUINT / 1000) + 1 seconds |
67 | * (49.7 days) would end wrapping to 704 milliseconds. |
68 | * |
69 | * Test that that isn't true anymore by scheduling two jobs: |
70 | * - one, as above |
71 | * - another that runs in 2100ms |
72 | * |
73 | * If everything is working properly, the 2100ms one should run first |
74 | * (and exit the mainloop). If we ever see the other job run |
75 | * then we have trouble (since it ran in less than 2 seconds). |
76 | * |
77 | * We need a timeout of at least 2 seconds because |
78 | * g_timeout_add_seconds() can add as much as an additional second of |
79 | * latency. |
80 | */ |
81 | g_test_bug (bug_uri_snippet: "https://gitlab.gnome.org/GNOME/glib/issues/1600" ); |
82 | loop = g_main_loop_new (NULL, FALSE); |
83 | |
84 | g_timeout_add (interval: 2100, function: stop_waiting, NULL); |
85 | interval_seconds = 1 + G_MAXUINT / 1000; |
86 | id = g_timeout_add_seconds (interval: interval_seconds, function: unreachable_callback, NULL); |
87 | |
88 | g_main_loop_run (loop); |
89 | g_main_loop_unref (loop); |
90 | |
91 | g_source_remove (tag: id); |
92 | } |
93 | |
94 | /* The ready_time for a GSource is stored as a gint64, as an absolute monotonic |
95 | * time in microseconds. To call poll(), this must be converted to a relative |
96 | * timeout, in milliseconds, as a gint. If the ready_time is sufficiently far |
97 | * in the future, the timeout will not fit. Previously, it would be narrowed in |
98 | * an implementation-defined way; if this gave a negative result, poll() would |
99 | * block forever. |
100 | * |
101 | * This test creates a GSource with the largest possible ready_time (a little |
102 | * over 292 millennia, assuming g_get_monotonic_time() starts from near 0 when |
103 | * the system boots), adds it to a GMainContext, queries it for the parameters |
104 | * to pass to poll() -- essentially the first half of |
105 | * g_main_context_iteration() -- and checks that the timeout is a large |
106 | * positive number. |
107 | */ |
108 | static void |
109 | test_far_future_ready_time (void) |
110 | { |
111 | GSourceFuncs source_funcs = { 0 }; |
112 | GMainContext *context = g_main_context_new (); |
113 | GSource *source = g_source_new (source_funcs: &source_funcs, struct_size: sizeof (GSource)); |
114 | gboolean acquired, ready; |
115 | gint priority, timeout_, n_fds; |
116 | |
117 | g_source_set_ready_time (source, G_MAXINT64); |
118 | g_source_attach (source, context); |
119 | |
120 | acquired = g_main_context_acquire (context); |
121 | g_assert_true (acquired); |
122 | |
123 | ready = g_main_context_prepare (context, priority: &priority); |
124 | g_assert_false (ready); |
125 | |
126 | n_fds = 0; |
127 | n_fds = g_main_context_query (context, max_priority: priority, timeout_: &timeout_, NULL, n_fds); |
128 | |
129 | g_assert_cmpint (n_fds, >=, 0); |
130 | |
131 | /* The true timeout in milliseconds doesn't fit into a gint. We definitely |
132 | * don't want poll() to block forever: |
133 | */ |
134 | g_assert_cmpint (timeout_, >=, 0); |
135 | /* Instead, we want it to block for as long as possible: */ |
136 | g_assert_cmpint (timeout_, ==, G_MAXINT); |
137 | |
138 | g_main_context_release (context); |
139 | g_main_context_unref (context); |
140 | g_source_unref (source); |
141 | } |
142 | |
143 | static gint64 last_time; |
144 | static gint count; |
145 | |
146 | static gboolean |
147 | test_func (gpointer data) |
148 | { |
149 | gint64 current_time; |
150 | |
151 | current_time = g_get_monotonic_time (); |
152 | |
153 | /* We accept 2 on the first iteration because _add_seconds() can |
154 | * have an initial latency of 1 second, see its documentation. |
155 | * |
156 | * Allow up to 500ms leeway for rounding and scheduling. |
157 | */ |
158 | if (count == 0) |
159 | g_assert_cmpint (current_time / 1000 - last_time / 1000, <=, 2500); |
160 | else |
161 | g_assert_cmpint (current_time / 1000 - last_time / 1000, <=, 1500); |
162 | |
163 | last_time = current_time; |
164 | count++; |
165 | |
166 | /* Make the timeout take up to 0.1 seconds. |
167 | * We should still get scheduled for the next second. |
168 | */ |
169 | g_usleep (microseconds: count * 10000); |
170 | |
171 | if (count < 10) |
172 | return TRUE; |
173 | |
174 | g_main_loop_quit (loop); |
175 | |
176 | return FALSE; |
177 | } |
178 | |
179 | static void |
180 | test_rounding (void) |
181 | { |
182 | loop = g_main_loop_new (NULL, FALSE); |
183 | |
184 | last_time = g_get_monotonic_time (); |
185 | g_timeout_add_seconds (interval: 1, function: test_func, NULL); |
186 | |
187 | g_main_loop_run (loop); |
188 | g_main_loop_unref (loop); |
189 | } |
190 | |
191 | int |
192 | main (int argc, char *argv[]) |
193 | { |
194 | g_test_init (argc: &argc, argv: &argv, NULL); |
195 | |
196 | g_test_add_func (testpath: "/timeout/seconds" , test_func: test_seconds); |
197 | g_test_add_func (testpath: "/timeout/weeks-overflow" , test_func: test_weeks_overflow); |
198 | g_test_add_func (testpath: "/timeout/far-future-ready-time" , test_func: test_far_future_ready_time); |
199 | g_test_add_func (testpath: "/timeout/rounding" , test_func: test_rounding); |
200 | |
201 | return g_test_run (); |
202 | } |
203 | |