Skip to content

Commit 980fe40

Browse files
committed
Do not poll when omitting timeout values for the monitor scanner.
Previously, it would default to use a 0 s timeout, which for select means that it doesn't block (it would poll continuously and waste CPU). This change unifies the make-udev-monitor/udev-monitor-set-timeout! interface with that of Guile's select procedure. * NEWS: Update news. * doc/guile-udev.texi (API Reference): Update doc. * examples/device-listener.scm (main) <make-udev-monitor>: Remove timeout arguments. * libguile-udev/udev-monitor-func.c (udev-monitor-set-timeout!): Make timeout arguments optional. Update doc. Streamline definition, removing all arguments validation, which is now handled by scm_select. * libguile-udev/udev-monitor-func.c (select_args_data): New struct. (call_select, false_on_exception): New procedures. (udev_monitor_scanner): Replace C select call with scm_select call, adjusting the rest accordingly. * libguile-udev/udev-monitor-type.h: Replace timeout member with new 'secs' and 'usecs' ones. * libguile-udev/udev-monitor-type.c (gudev_monitor_to_scm): Set default values of secs and usecs to SCM_BOOL_F. * modules/udev/monitor.scm (make-udev-monitor): Do not specify default values for filter, timeout-sec and timeout-usec keyword arguments. Update doc. Fixes: #5
1 parent 3187ba7 commit 980fe40

File tree

7 files changed

+105
-63
lines changed

7 files changed

+105
-63
lines changed

NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ Targets are:
4343
- Document Guile-Udev types.
4444

4545
Thanks to Maxim Cournoyer <[email protected]>.
46+
** Change the default the timeout values of `make-udev-monitor' to #f
47+
The TIMEOUT-SEC and TIMEOUT-USEC arguments now default to #f instead of 0,
48+
which means that the `select' call no longer polls by default, which would
49+
consume 100% of a CPU core (see:
50+
https://github.com/artyom-poptsov/guile-udev/issues/5).
4651

4752
* Changes in version 0.2.4 (2022-12-27)
4853
** Bugfix: Check every string that comes from the udev for NULL

doc/guile-udev.texi

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,15 @@ Monitor object allows to scan for device events. When an event occurs, the
182182
monitor calls a specified callback. Inside the scanner loop, the monitor
183183
calls @code{select} procedure to check if any events available. The
184184
@code{select} timeout can be set by @code{udev-monitor-set-timeout!}
185-
procedure. The timeout seconds and milliseconds parts are set to zero by
186-
default.
185+
procedure. By default, no timeout is used, which means the monitor will wait
186+
forever for an event to be triggered.
187+
188+
@quotation note
189+
The monitor scanner loops infinitely; that is, even when a timeout value is
190+
used, the scanner will keep monitoring events, but will poll at the requested
191+
period. For maximum efficiency, it is recommended to not use a timeout and
192+
let the underlying @code{select} system call indefinitely wait for an event.
193+
@end quotation
187194

188195
These procedures are available from the module @code{(udev monitor)}.
189196

@@ -233,28 +240,34 @@ Example:
233240
(make-udev-monitor udev #:filter (list "usb" "usb_device"))
234241
@end lisp
235242
@item timeout-sec
236-
Timeout for 'select' call in the event listener. This part of the timeout is
237-
specified in seconds.
243+
Optional timeout for 'select' call in the event listener. This part of the
244+
timeout is specified in seconds.
238245

239246
Expected type: Non-negative integer.
240247

241-
Default value: 0
248+
Default value: @code{#f}
249+
250+
Examples:
251+
252+
@lisp
253+
(make-udev-monitor udev) ;no timeout, most efficient
254+
@end lisp
242255

243-
Example:
244256
@lisp
245-
(make-udev-monitor udev #:timeout-sec 1)
257+
(make-udev-monitor udev #:timeout-sec 3.33) ;3.33 s poll period
246258
@end lisp
259+
247260
@item timeout-usec
248-
Timeout for 'select' call in the event listener. This part of the timeout is
249-
specified in milliseconds.
261+
Optional timeout for 'select' call in the event listener, in microseconds.
262+
The @var{timeout-sec} argument must be specified as well otherwise
263+
@var{timeout-usec} is ignored.
250264

251265
Expected type: Non-negative integer.
252266

253-
Default value: 0
267+
Default value: @code{#f}
254268

255-
Example:
256269
@lisp
257-
(make-udev-monitor udev #:timeout-usec 1)
270+
(make-udev-monitor udev #:timeout-sec 0 #:timeout-usec 250) ;250 us poll period
258271
@end lisp
259272
@end table
260273

@@ -278,10 +291,12 @@ Remove all the filters that was set for @var{udev-monitor}. Throws
278291
@code{guile-udev-error} on errors.
279292
@end deffn
280293

281-
@deffn {Scheme Procedure} udev-monitor-set-timeout! udev-monitor seconds milliseconds
282-
Set the @var{udev-monitor} udev event polling timeout. The @var{seconds} and
283-
@var{milliseconds} parameters must be integer numbers greater or equal to
284-
zero.
294+
@deffn {Scheme Procedure} udev-monitor-set-timeout! udev-monitor [secs] [usecs]
295+
Set the @var{udev-monitor} udev event polling timeout. The @var{secs}
296+
parameter must be a non-negative integer or float representing seconds, while
297+
@var{usecs} must be a non-negative integer representing microseconds. To
298+
clear the timeout value, @var{secs} may be set to @code{#f} or omitted
299+
entirely.
285300
@end deffn
286301

287302
@deffn {Scheme Procedure} udev-monitor-set-callback! udev-monitor callback

examples/device-listener.scm

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,8 @@
1313
(define (main args)
1414
(let* ((udev (make-udev))
1515
(udev-monitor (make-udev-monitor udev
16-
#:timeout-sec 1
17-
#:timeout-usec 0
18-
#:callback callback
19-
#:filter (list "usb"
20-
"usb_device"))))
16+
#:callback callback
17+
#:filter (list "usb" "usb_device"))))
2118
(udev-monitor-start-scanning! udev-monitor)
2219
(while #t
2320
(sleep 1))))

libguile-udev/udev-monitor-func.c

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -140,50 +140,56 @@ SCM_DEFINE_N(gudev_monitor_set_error_callback_x, "udev-monitor-set-error-callbac
140140
}
141141
#undef FUNC_NAME
142142

143-
SCM_DEFINE_N(gudev_monitor_set_timeout_x, "udev-monitor-set-timeout!", 3,
144-
(SCM udev_monitor, SCM seconds, SCM milliseconds),
145-
"Set monitor event poll timeout. \
146-
Throws 'guile-udev-error' on errors.")
143+
SCM_DEFINE(gudev_monitor_set_timeout_x, "udev-monitor-set-timeout!", 1, 2, 0,
144+
(SCM udev_monitor, SCM secs, SCM usecs),
145+
"Set monitor event poll @var{seconds} and @var{microseconds}\n"
146+
"timeout non-negative numbers in seconds and microseconds,\n"
147+
"respectively.\n\n"
148+
149+
"@var{secs} and @var{usecs} are optional;\n"
150+
"They share the same semantic as the corresponding arguments of\n"
151+
"Guile's `select' abstraction. When left unspecified, the timeout\n"
152+
"is cleared.\n\n"
153+
154+
"Throws 'guile-udev-error' on errors.")
147155
#define FUNC_NAME s_gudev_monitor_set_timeout_x
148156
{
149157
gudev_monitor_t* umd = gudev_monitor_from_scm(udev_monitor);
150-
SCM_ASSERT(scm_number_p(seconds), seconds, SCM_ARG2, FUNC_NAME);
151-
SCM_ASSERT(scm_number_p(milliseconds), milliseconds, SCM_ARG3, FUNC_NAME);
152-
long c_seconds = scm_to_long(seconds);
153-
long c_milliseconds = scm_to_long(milliseconds);
154-
155-
if (c_seconds < 0) {
156-
guile_udev_error1(
157-
FUNC_NAME,
158-
"'Seconds' part of the timeout must be >= 0.",
159-
seconds);
160-
}
161-
162-
if (c_milliseconds < 0) {
163-
guile_udev_error1(
164-
FUNC_NAME,
165-
"'Milliseconds' part of the timeout must be >= 0.",
166-
milliseconds);
167-
}
168-
169-
umd->timeout.tv_sec = c_seconds;
170-
umd->timeout.tv_usec = c_milliseconds;
158+
umd->secs = secs;
159+
umd->usecs = usecs;
171160

172161
scm_remember_upto_here_1(udev_monitor);
173162

174163
return SCM_UNDEFINED;
175164
}
176165
#undef FUNC_NAME
177166

167+
struct select_args_data {
168+
SCM reads;
169+
SCM secs;
170+
SCM usecs;
171+
};
172+
173+
/* A procedure for calling scm_select via scm_internal_catch. */
174+
SCM call_select(void *data) {
175+
struct select_args_data* args = data;
176+
return scm_select(args->reads, SCM_EOL, SCM_EOL, args->secs, args->usecs);
177+
}
178+
179+
/* Dummy handler returning false. */
180+
SCM false_on_exception(void *data, SCM key, SCM args) {
181+
return SCM_BOOL_F;
182+
}
183+
178184
void* udev_monitor_scanner(void* arg)
179185
#define FUNC_NAME "udev_monitor_scanner"
180186
{
181187
SCM udev_monitor = (SCM) arg;
182188
gudev_monitor_t* umd = gudev_monitor_from_scm(udev_monitor);
183-
fd_set fd_set_;
184189
int result;
185-
int select_result;
186-
int monitor_fd;
190+
SCM select_result;
191+
int c_monitor_fd;
192+
SCM monitor_fd;
187193
struct udev_device *dev;
188194
SCM error_callback = umd->error_callback;
189195

@@ -198,7 +204,9 @@ void* udev_monitor_scanner(void* arg)
198204
return NULL;
199205
}
200206

201-
monitor_fd = udev_monitor_get_fd(umd->udev_monitor);
207+
c_monitor_fd = udev_monitor_get_fd(umd->udev_monitor);
208+
monitor_fd = scm_from_int(c_monitor_fd);
209+
202210
if (monitor_fd < 0) {
203211
char msg[] = "Could not retrieve udev monitor file descriptor.";
204212
scm_call_2(error_callback, udev_monitor,
@@ -207,29 +215,41 @@ void* udev_monitor_scanner(void* arg)
207215
return NULL;
208216
}
209217

218+
struct select_args_data select_args;
219+
select_args.reads = scm_list_1(monitor_fd);
220+
select_args.secs = umd->secs;
221+
select_args.usecs = umd->usecs;
222+
210223
SCM callback = umd->scanner_callback;
211224
SCM device;
225+
SCM read_fdes;
212226

213227
while (1) {
214-
struct timeval timeout = umd->timeout;
215228
pthread_mutex_lock(&umd->lock);
216229
if (! umd->is_scanning) {
217230
break;
218231
}
219232
pthread_mutex_unlock(&umd->lock);
220233

221-
FD_ZERO(&fd_set_);
222-
FD_SET(monitor_fd, &fd_set_);
223-
select_result = select(monitor_fd + 1, &fd_set_, NULL, NULL, &timeout);
224-
if (select_result == -1) {
234+
/* We use scm_select here instead of C select so as to benefit from
235+
* the 'secs' and 'usecs' arguments validation/behavior that it
236+
* provides. */
237+
select_result = scm_internal_catch(scm_from_utf8_symbol("system-error"),
238+
call_select, &select_args,
239+
false_on_exception, NULL);
240+
// Actual error handling is done here.
241+
if (scm_is_false(select_result)) {
225242
char msg[] = "Error during 'select' call.";
226243
scm_call_2(error_callback, udev_monitor,
227244
scm_from_locale_string(msg));
228245
umd->is_scanning = 0;
229246
pthread_cancel(pthread_self());
230247
break;
231248
}
232-
if (FD_ISSET(monitor_fd, &fd_set_)) {
249+
250+
read_fdes = scm_car(select_result);
251+
252+
if (scm_member(monitor_fd, read_fdes)) {
233253
dev = udev_monitor_receive_device(umd->udev_monitor);
234254
device = udev_device_to_scm(umd->udev, dev);
235255
scm_call_1(callback, device);

libguile-udev/udev-monitor-type.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ SCM gudev_monitor_to_scm(SCM udev, struct udev_monitor *udev_monitor)
8181
gudev_monitor_t* umd = make_udev_monitor();
8282
umd->udev = udev;
8383
umd->udev_monitor = udev_monitor;
84-
umd->timeout.tv_sec = 0;
85-
umd->timeout.tv_usec = 0;
84+
umd->secs = SCM_BOOL_F;
85+
umd->usecs = SCM_BOOL_F;
8686
umd->is_scanning = 0;
8787
umd->scanner_callback = SCM_BOOL_F;
8888
pthread_mutex_init(&umd->lock, NULL);

libguile-udev/udev-monitor-type.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ struct gudev_monitor {
4545
/**
4646
* @brief timeout -- 'select' timeout for the device monitor.
4747
*/
48-
struct timeval timeout;
48+
SCM secs;
49+
SCM usecs;
4950

5051
pthread_mutex_t lock;
5152
pthread_t scanner_thread;

modules/udev/monitor.scm

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,18 @@
5050
(format (current-error-port)
5151
"ERROR: in ~a: ~a~%"
5252
monitor error-message)))
53-
(filter #f)
54-
(timeout-usec 0)
55-
(timeout-sec 0))
53+
filter
54+
timeout-sec
55+
timeout-usec)
5656
"Create a new 'udev-monitor' object configured with the specified parameters.
5757
CALLBACK is a one argument procedure (receiving a 'udev-device' object) called
5858
when an event matching the specified FILTER. FILTER is a list whose first
5959
element is the device subsystem, and whose second argument is the device type,
60-
or #f to match any type."
60+
or #f to match any type. If a timeout is desired, TIMEOUT-SEC or both
61+
TIMEOUT-SEC and TIMEOUT-USEC may be provided as non-negative numbers of
62+
seconds and microseconds, respectively. If TIMEOUT-USEC, is used, TIMEOUT-SEC
63+
must also have a value, else it is ignored, as for the 'secs' and 'usecs'
64+
argument of Guile's 'select' procedure."
6165
(let ((monitor (%make-udev-monitor udev)))
6266
(udev-monitor-set-timeout! monitor timeout-sec timeout-usec)
6367
(udev-monitor-set-callback! monitor callback)

0 commit comments

Comments
 (0)