wdm 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,28 +1,28 @@
1
- #ifndef WDM_RB_CHANGE_H
2
- #define WDM_RB_CHANGE_H
3
-
4
- #ifdef __cplusplus
5
- extern "C" {
6
- #endif // __cplusplus
7
-
8
- // ----------------------------------------------------------
9
- // Global variables
10
- // ----------------------------------------------------------
11
-
12
- extern VALUE cWDM_Change;
13
-
14
- // ---------------------------------------------------------
15
- // Prototypes
16
- // ---------------------------------------------------------
17
-
18
- VALUE wdm_rb_change_new_from_notification(const LPWSTR, const PFILE_NOTIFY_INFORMATION);
19
-
20
- void wdm_rb_change_init();
21
-
22
- // ---------------------------------------------------------
23
-
24
- #ifdef __cplusplus
25
- }
26
- #endif // __cplusplus
27
-
1
+ #ifndef WDM_RB_CHANGE_H
2
+ #define WDM_RB_CHANGE_H
3
+
4
+ #ifdef __cplusplus
5
+ extern "C" {
6
+ #endif // __cplusplus
7
+
8
+ // ----------------------------------------------------------
9
+ // Global variables
10
+ // ----------------------------------------------------------
11
+
12
+ extern VALUE cWDM_Change;
13
+
14
+ // ---------------------------------------------------------
15
+ // Prototypes
16
+ // ---------------------------------------------------------
17
+
18
+ VALUE wdm_rb_change_new_from_notification(const LPWSTR, const PFILE_NOTIFY_INFORMATION);
19
+
20
+ void wdm_rb_change_init();
21
+
22
+ // ---------------------------------------------------------
23
+
24
+ #ifdef __cplusplus
25
+ }
26
+ #endif // __cplusplus
27
+
28
28
  #endif // WDM_RB_CHANGE_H
@@ -1,571 +1,575 @@
1
- #include "wdm.h"
2
-
3
- #include "utils.h"
4
- #include "entry.h"
5
- #include "queue.h"
6
- #include "monitor.h"
7
-
8
- #include "rb_change.h"
9
- #include "rb_monitor.h"
10
-
11
- // ----------------------------------------------------------
12
- // Global variables
13
- // ----------------------------------------------------------
14
-
15
- VALUE cWDM_Monitor;
16
-
17
- VALUE eWDM_UnknownFlagError;
18
-
19
- // ----------------------------------------------------------
20
- // Cached variables
21
- // ----------------------------------------------------------
22
-
23
- static ID wdm_rb_sym_call;
24
- static ID wdm_rb_sym_files;
25
- static ID wdm_rb_sym_directories;
26
- static ID wdm_rb_sym_attributes;
27
- static ID wdm_rb_sym_size;
28
- static ID wdm_rb_sym_last_write;
29
- static ID wdm_rb_sym_last_access;
30
- static ID wdm_rb_sym_creation;
31
- static ID wdm_rb_sym_security;
32
- static ID wdm_rb_sym_default;
33
-
34
- // ---------------------------------------------------------
35
- // Prototypes of static functions
36
- // ---------------------------------------------------------
37
-
38
- static void monitor_mark(LPVOID);
39
- static void monitor_free(LPVOID);
40
- static VALUE rb_monitor_alloc(VALUE);
41
-
42
- static DWORD id_to_flag(ID);
43
- static DWORD extract_flags_from_rb_array(VALUE);
44
- static VALUE combined_watch(BOOL, int, VALUE*, VALUE);
45
- static VALUE rb_monitor_watch(int, VALUE*, VALUE);
46
-
47
- static VALUE rb_monitor_watch_recursively(int, VALUE*, VALUE);
48
-
49
- static void CALLBACK handle_entry_change(DWORD, DWORD, LPOVERLAPPED);
50
- static BOOL register_monitoring_entry(WDM_PEntry);
51
- static DWORD WINAPI start_monitoring(LPVOID);
52
-
53
- static VALUE wait_for_changes(LPVOID);
54
- static void process_changes(WDM_PQueue);
55
- static void stop_monitoring(LPVOID);
56
- static VALUE rb_monitor_run_bang(VALUE);
57
-
58
- static VALUE rb_monitor_stop(VALUE);
59
-
60
- // ----------------------------------------------------------
61
-
62
- static void
63
- monitor_mark(LPVOID param)
64
- {
65
- WDM_PMonitor monitor;
66
- WDM_PEntry entry;
67
-
68
- monitor = (WDM_PMonitor)param;
69
- entry = monitor->head;
70
-
71
- while(entry != NULL) {
72
- rb_gc_mark(entry->user_data->callback);
73
- entry = entry->next;
74
- }
75
- }
76
-
77
- static void
78
- monitor_free(LPVOID param)
79
- {
80
- WDM_PMonitor monitor;
81
- WDM_PEntry entry;
82
-
83
- WDM_DEBUG("Freeing a monitor object!");
84
-
85
- monitor = (WDM_PMonitor)param;
86
- entry = monitor->head;
87
-
88
- stop_monitoring(monitor); // If the monitor is already stopped, it would do nothing
89
-
90
- while(entry != NULL) {
91
- if ( entry->event_container.hEvent != NULL ) {
92
- wdm_monitor_callback_param_free(
93
- (WDM_PMonitorCallbackParam)entry->event_container.hEvent
94
- );
95
- }
96
- entry = entry->next;
97
- }
98
-
99
- wdm_monitor_free(monitor);
100
- }
101
-
102
- static VALUE
103
- rb_monitor_alloc(VALUE self)
104
- {
105
- WDM_DEBUG("--------------------------------");
106
- WDM_DEBUG("Allocating a new monitor object!");
107
- WDM_DEBUG("--------------------------------");
108
-
109
- return Data_Wrap_Struct(self, monitor_mark, monitor_free, wdm_monitor_new());
110
- }
111
-
112
- static DWORD
113
- id_to_flag(ID id)
114
- {
115
- if ( id == wdm_rb_sym_default ) return WDM_MONITOR_FLAGS_DEFAULT;
116
-
117
- // TODO: Maybe reorder the if's in the frequency of use for better performance?
118
- if ( id == wdm_rb_sym_files ) return FILE_NOTIFY_CHANGE_FILE_NAME;
119
- if ( id == wdm_rb_sym_directories ) return FILE_NOTIFY_CHANGE_DIR_NAME;
120
- if ( id == wdm_rb_sym_attributes ) return FILE_NOTIFY_CHANGE_ATTRIBUTES;
121
- if ( id == wdm_rb_sym_size ) return FILE_NOTIFY_CHANGE_SIZE;
122
- if ( id == wdm_rb_sym_last_write ) return FILE_NOTIFY_CHANGE_LAST_WRITE;
123
- if ( id == wdm_rb_sym_last_access ) return FILE_NOTIFY_CHANGE_LAST_ACCESS;
124
- if ( id == wdm_rb_sym_creation ) return FILE_NOTIFY_CHANGE_CREATION;
125
- if ( id == wdm_rb_sym_security ) return FILE_NOTIFY_CHANGE_SECURITY;
126
-
127
- rb_raise(eWDM_UnknownFlagError, "Unknown watch flag: ':%s'", rb_id2name(id));
128
- }
129
-
130
- static DWORD
131
- extract_flags_from_rb_array(VALUE flags_array)
132
- {
133
- VALUE flag_symbol;
134
- DWORD flags;
135
-
136
- flags = 0;
137
-
138
- while ( RARRAY_LEN(flags_array) != 0 ) {
139
- flag_symbol = rb_ary_pop(flags_array);
140
- Check_Type(flag_symbol, T_SYMBOL);
141
- flags |= id_to_flag( SYM2ID(flag_symbol) );
142
- }
143
-
144
- return flags;
145
- }
146
-
147
- static VALUE
148
- combined_watch(BOOL recursively, int argc, VALUE *argv, VALUE self)
149
- {
150
- WDM_PMonitor monitor;
151
- WDM_PEntry entry;
152
- int directory_letters_count;
153
- VALUE directory, flags, os_encoded_directory;
154
- BOOL running;
155
-
156
- // TODO: Maybe raise a more user-friendly error?
157
- rb_need_block();
158
-
159
- Data_Get_Struct(self, WDM_Monitor, monitor);
160
-
161
- EnterCriticalSection(&monitor->lock);
162
- running = monitor->running;
163
- LeaveCriticalSection(&monitor->lock);
164
-
165
- if ( running ) {
166
- rb_raise(eWDM_MonitorRunningError, "You can't watch new directories while the monitor is running!");
167
- }
168
-
169
- rb_scan_args(argc, argv, "1*", &directory, &flags);
170
-
171
- Check_Type(directory, T_STRING);
172
-
173
- entry = wdm_entry_new();
174
- entry->user_data->watch_childeren = recursively;
175
- entry->user_data->callback = rb_block_proc();
176
- entry->user_data->flags = RARRAY_LEN(flags) == 0 ? WDM_MONITOR_FLAGS_DEFAULT : extract_flags_from_rb_array(flags);
177
-
178
- // WTF Ruby source: The original code (file.c) uses the following macro to make sure that the encoding
179
- // of the string is ASCII-compatible, but UTF-16LE (Windows default encoding) is not!!!
180
- //
181
- // FilePathValue(directory);
182
-
183
- os_encoded_directory = rb_str_encode_ospath(directory);
184
-
185
- // RSTRING_LEN can't be used because it would return the count of bytes the string uses in its encoding (like UTF-8).
186
- // UTF-8 might use more than one byte for the char, which is not needed for WCHAR strings.
187
- // Also, the result of MultiByteToWideChar _includes_ the NULL char at the end, which is not true for RSTRING.
188
- //
189
- // Example: 'C:\Users\Maher\Desktop\تجربة' with __ENCODING__ == UTF-8
190
- // MultiByteToWideChar => 29 (28-char + null)
191
- // RSTRING_LEN => 33 (23-char + 10-bytes for 5 Arabic letters which take 2 bytes each)
192
- //
193
- directory_letters_count = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(os_encoded_directory), -1, NULL, 0);
194
-
195
- entry->user_data->dir = ALLOCA_N(WCHAR, directory_letters_count);
196
-
197
- MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(os_encoded_directory), -1, entry->user_data->dir, directory_letters_count);
198
-
199
- WDM_WDEBUG("New path to watch: '%s'", entry->user_data->dir);
200
-
201
- entry->user_data->dir = wdm_utils_full_pathname(entry->user_data->dir);
202
-
203
- if ( entry->user_data->dir == 0 ) {
204
- wdm_entry_free(entry);
205
- rb_raise(eWDM_Error, "Can't get the absolute path for the passed directory: '%s'!", RSTRING_PTR(directory));
206
- }
207
-
208
- if ( ! wdm_utils_unicode_is_directory(entry->user_data->dir) ) {
209
- wdm_entry_free(entry);
210
- rb_raise(eWDM_InvalidDirectoryError, "No such directory: '%s'!", RSTRING_PTR(directory));
211
- }
212
-
213
- // Tell the GC to collect the tmp string
214
- rb_str_resize(os_encoded_directory, 0);
215
-
216
- entry->dir_handle = CreateFileW(
217
- entry->user_data->dir, // pointer to the file name
218
- FILE_LIST_DIRECTORY, // access (read/write) mode
219
- FILE_SHARE_READ // share mode
220
- | FILE_SHARE_WRITE
221
- | FILE_SHARE_DELETE,
222
- NULL, // security descriptor
223
- OPEN_EXISTING, // how to create
224
- FILE_FLAG_BACKUP_SEMANTICS
225
- | FILE_FLAG_OVERLAPPED, // file attributes
226
- NULL
227
- );
228
-
229
- if ( entry->dir_handle == INVALID_HANDLE_VALUE ) {
230
- wdm_entry_free(entry);
231
- rb_raise(eWDM_Error, "Can't watch directory: '%s'!", RSTRING_PTR(directory));
232
- }
233
-
234
- // Store a reference to the entry instead of an event as the event
235
- // won't be used when using callbacks.
236
- entry->event_container.hEvent = wdm_monitor_callback_param_new(monitor, entry);
237
-
238
- wdm_monitor_update_head(monitor, entry);
239
-
240
- WDM_WDEBUG("Watching directory: '%s'", entry->user_data->dir);
241
-
242
- return Qnil;
243
- }
244
-
245
- static VALUE
246
- rb_monitor_watch(int argc, VALUE *argv, VALUE self)
247
- {
248
- return combined_watch(FALSE, argc, argv, self);
249
- }
250
-
251
- static VALUE
252
- rb_monitor_watch_recursively(int argc, VALUE *argv, VALUE self)
253
- {
254
- return combined_watch(TRUE, argc, argv, self);
255
- }
256
-
257
- static void CALLBACK
258
- handle_entry_change(
259
- DWORD err_code, // completion code
260
- DWORD bytes_transfered, // number of bytes transferred
261
- LPOVERLAPPED event_container // I/O information buffer
262
- ) {
263
- WDM_PMonitorCallbackParam param;
264
- WDM_PQueueItem data_to_process;
265
-
266
- if ( err_code == ERROR_OPERATION_ABORTED ) {
267
- // Async operation was canceled. This shouldn't happen.
268
- // TODO:
269
- // 1. Maybe add a union in the queue for errors?
270
- // 2. What's the best action when this happens?
271
- WDM_DEBUG("Dir handler closed in the process callback!");
272
- return;
273
- }
274
-
275
- if ( ! bytes_transfered ) {
276
- WDM_DEBUG("Buffer overflow?! Changes are bigger than the buffer!");
277
- return;
278
- }
279
-
280
- param = (WDM_PMonitorCallbackParam)event_container->hEvent;
281
- data_to_process = wdm_queue_item_new(WDM_QUEUE_ITEM_TYPE_DATA);
282
- data_to_process->data = wdm_queue_item_data_new();
283
-
284
- WDM_WDEBUG("Change detected in '%s'", param->entry->user_data->dir);
285
-
286
- data_to_process->data->user_data = param->entry->user_data;
287
-
288
- // Copy change data to the backup buffer
289
- memcpy(data_to_process->data->buffer, param->entry->buffer, bytes_transfered);
290
-
291
- // Add the backup buffer to the change queue
292
- wdm_queue_enqueue(param->monitor->changes, data_to_process);
293
-
294
- // Resume watching the dir for changes
295
- register_monitoring_entry(param->entry);
296
-
297
- // Tell the processing thread to process the changes
298
- if ( WaitForSingleObject(param->monitor->process_event, 0) != WAIT_OBJECT_0 ) { // Check if already signaled
299
- SetEvent(param->monitor->process_event);
300
- }
301
- }
302
-
303
- static BOOL
304
- register_monitoring_entry(WDM_PEntry entry)
305
- {
306
- BOOL success;
307
- DWORD bytes;
308
- bytes = 0; // Not used because the process callback gets passed the written bytes
309
-
310
- success = ReadDirectoryChangesW(
311
- entry->dir_handle, // handle to directory
312
- entry->buffer, // read results buffer
313
- WDM_BUFFER_SIZE, // length of buffer
314
- entry->user_data->watch_childeren, // monitoring option
315
- entry->user_data->flags, // filter conditions
316
- &bytes, // bytes returned
317
- &entry->event_container, // overlapped buffer
318
- &handle_entry_change // process callback
319
- );
320
-
321
- if ( ! success ) {
322
- WDM_DEBUG("ReadDirectoryChangesW failed with error (%d): %s", GetLastError(), rb_w32_strerror(GetLastError()));
323
- return FALSE;
324
- }
325
-
326
- return TRUE;
327
- }
328
-
329
- static DWORD WINAPI
330
- start_monitoring(LPVOID param)
331
- {
332
- WDM_PMonitor monitor;
333
- WDM_PEntry curr_entry;
334
-
335
- monitor = (WDM_PMonitor)param;
336
- curr_entry = monitor->head;
337
-
338
- WDM_DEBUG("Starting the monitoring thread!");
339
-
340
- while(curr_entry != NULL) {
341
- if ( ! register_monitoring_entry(curr_entry) ) {
342
- WDM_PQueueItem error_item;
343
- int directory_bytes;
344
- LPSTR multibyte_directory;
345
-
346
- directory_bytes = WideCharToMultiByte(CP_UTF8, 0, curr_entry->user_data->dir, -1, NULL, 0, NULL, NULL);
347
- multibyte_directory = ALLOCA_N(CHAR, directory_bytes);
348
- WideCharToMultiByte(CP_UTF8, 0, curr_entry->user_data->dir, -1, multibyte_directory, directory_bytes, NULL, NULL);
349
-
350
- error_item = wdm_queue_item_new(WDM_QUEUE_ITEM_TYPE_ERROR);
351
- error_item->error = wdm_queue_item_error_new(
352
- eWDM_UnwatchableDirectoryError, "Can't watch directory: '%s'!", multibyte_directory
353
- );
354
-
355
- wdm_queue_enqueue(monitor->changes, error_item);
356
- SetEvent(monitor->process_event);
357
- }
358
-
359
- curr_entry = curr_entry->next;
360
- }
361
-
362
- while(monitor->running) {
363
- // TODO: Is this the best way to do it?
364
- if ( WaitForSingleObjectEx(monitor->stop_event, INFINITE, TRUE) == WAIT_OBJECT_0) {
365
- WDM_DEBUG("Exiting the monitoring thread!");
366
- ExitThread(0);
367
- }
368
- }
369
-
370
- return 0;
371
- }
372
-
373
- static VALUE
374
- wait_for_changes(LPVOID param)
375
- {
376
- HANDLE process_event;
377
-
378
- process_event = (HANDLE)param;
379
-
380
- return WaitForSingleObject(process_event, INFINITE) == WAIT_OBJECT_0 ? Qtrue : Qfalse;
381
- }
382
-
383
- static void
384
- process_changes(WDM_PQueue changes)
385
- {
386
- WDM_PQueueItem item;
387
- LPBYTE current_info_entry_offset;
388
- PFILE_NOTIFY_INFORMATION info;
389
- VALUE event;
390
-
391
- WDM_DEBUG("---------------------------");
392
- WDM_DEBUG("Process changes");
393
- WDM_DEBUG("--------------------------");
394
-
395
- while( ! wdm_queue_is_empty(changes) ) {
396
- item = wdm_queue_dequeue(changes);
397
-
398
- if ( item->type == WDM_QUEUE_ITEM_TYPE_ERROR ) {
399
- rb_raise(item->error->exception_klass, item->error->message);
400
- }
401
- else {
402
- current_info_entry_offset = (LPBYTE)item->data->buffer;
403
-
404
- for(;;) {
405
- info = (PFILE_NOTIFY_INFORMATION)current_info_entry_offset;
406
- event = wdm_rb_change_new_from_notification(item->data->user_data->dir, info);
407
-
408
- WDM_DEBUG("---------------------------");
409
- WDM_DEBUG("Running user callback");
410
- WDM_DEBUG("--------------------------");
411
-
412
- rb_funcall(item->data->user_data->callback, wdm_rb_sym_call, 1, event);
413
-
414
- WDM_DEBUG("---------------------------");
415
-
416
- if ( ! info->NextEntryOffset ) break;
417
-
418
- current_info_entry_offset += info->NextEntryOffset;
419
- }
420
- }
421
-
422
- wdm_queue_item_free(item);
423
- }
424
- }
425
-
426
- static void
427
- stop_monitoring(LPVOID param)
428
- {
429
- BOOL already_stopped;
430
- WDM_PMonitor monitor;
431
- WDM_PEntry entry;
432
-
433
- monitor = (WDM_PMonitor)param;
434
- already_stopped = FALSE;
435
-
436
- WDM_DEBUG("Stopping the monitor!");
437
-
438
- EnterCriticalSection(&monitor->lock);
439
- if ( ! monitor->running ) {
440
- already_stopped = TRUE;
441
- }
442
- else {
443
- monitor->running = FALSE;
444
- }
445
- LeaveCriticalSection(&monitor->lock);
446
-
447
- if (already_stopped) {
448
- WDM_DEBUG("Can't stop monitoring because it's already stopped (or it's never been started)!!");
449
- return;
450
- }
451
-
452
- entry = monitor->head;
453
-
454
- while(entry != NULL) {
455
- CancelIo(entry->dir_handle); // Stop monitoring changes
456
- entry = entry->next;
457
- }
458
-
459
- SetEvent(monitor->stop_event);
460
- SetEvent(monitor->process_event); // The process code checks after the wait for an exit signal
461
- WaitForSingleObject(monitor->monitoring_thread, 10000);
462
- }
463
-
464
- static VALUE
465
- rb_monitor_run_bang(VALUE self)
466
- {
467
- BOOL already_running,
468
- waiting_succeeded;
469
- WDM_PMonitor monitor;
470
-
471
- WDM_DEBUG("Running the monitor!");
472
-
473
- Data_Get_Struct(self, WDM_Monitor, monitor);
474
- already_running = FALSE;
475
-
476
- EnterCriticalSection(&monitor->lock);
477
- if ( monitor->running ) {
478
- already_running = TRUE;
479
- }
480
- else {
481
- monitor->running = TRUE;
482
- }
483
- LeaveCriticalSection(&monitor->lock);
484
-
485
- if (already_running) {
486
- WDM_DEBUG("Not doing anything because the monitor is already running!");
487
- return Qnil;
488
- }
489
-
490
- // Reset events
491
- ResetEvent(monitor->process_event);
492
- ResetEvent(monitor->stop_event);
493
-
494
- monitor->monitoring_thread = CreateThread(
495
- NULL, // default security attributes
496
- 0, // use default stack size
497
- start_monitoring, // thread function name
498
- monitor, // argument to thread function
499
- 0, // use default creation flags
500
- NULL // Ignore thread identifier
501
- );
502
-
503
- if ( monitor->monitoring_thread == NULL ) {
504
- rb_raise(eWDM_Error, "Can't create a thread for the monitor!");
505
- }
506
-
507
- while ( monitor->running ) {
508
- waiting_succeeded = rb_thread_blocking_region(wait_for_changes, monitor->process_event, stop_monitoring, monitor);
509
-
510
- if ( waiting_succeeded == Qfalse ) {
511
- rb_raise(eWDM_Error, "Failed while waiting for a change in the watched directories!");
512
- }
513
-
514
- if ( ! monitor->running ) {
515
- wdm_queue_empty(monitor->changes);
516
- return Qnil;
517
- }
518
-
519
- process_changes(monitor->changes);
520
-
521
- if ( ! ResetEvent(monitor->process_event) ) {
522
- rb_raise(eWDM_Error, "Couldn't reset system events to watch for changes!");
523
- }
524
- }
525
-
526
- return Qnil;
527
- }
528
-
529
- static VALUE
530
- rb_monitor_stop(VALUE self)
531
- {
532
- WDM_PMonitor monitor;
533
-
534
- Data_Get_Struct(self, WDM_Monitor, monitor);
535
-
536
- stop_monitoring(monitor);
537
-
538
- WDM_DEBUG("Stopped the monitor!");
539
-
540
- return Qnil;
541
- }
542
-
543
- void
544
- wdm_rb_monitor_init()
545
- {
546
- WDM_DEBUG("Registering WDM::Monitor with Ruby!");
547
-
548
- wdm_rb_sym_call = rb_intern("call");
549
- wdm_rb_sym_files = rb_intern("files");
550
- wdm_rb_sym_directories = rb_intern("directories");
551
- wdm_rb_sym_attributes = rb_intern("attributes");
552
- wdm_rb_sym_size = rb_intern("size");
553
- wdm_rb_sym_last_write = rb_intern("last_write");
554
- wdm_rb_sym_last_access = rb_intern("last_access");
555
- wdm_rb_sym_creation = rb_intern("creation");
556
- wdm_rb_sym_security = rb_intern("security");
557
- wdm_rb_sym_default = rb_intern("default");
558
-
559
- eWDM_MonitorRunningError = rb_define_class_under(mWDM, "MonitorRunningError", eWDM_Error);
560
- eWDM_InvalidDirectoryError = rb_define_class_under(mWDM, "InvalidDirectoryError", eWDM_Error);
561
- eWDM_UnknownFlagError = rb_define_class_under(mWDM, "UnknownFlagError", eWDM_Error);
562
- eWDM_UnwatchableDirectoryError = rb_define_class_under(mWDM, "UnwatchableDirectoryError", eWDM_Error);
563
-
564
- cWDM_Monitor = rb_define_class_under(mWDM, "Monitor", rb_cObject);
565
-
566
- rb_define_alloc_func(cWDM_Monitor, rb_monitor_alloc);
567
- rb_define_method(cWDM_Monitor, "watch", RUBY_METHOD_FUNC(rb_monitor_watch), -1);
568
- rb_define_method(cWDM_Monitor, "watch_recursively", RUBY_METHOD_FUNC(rb_monitor_watch_recursively), -1);
569
- rb_define_method(cWDM_Monitor, "run!", RUBY_METHOD_FUNC(rb_monitor_run_bang), 0);
570
- rb_define_method(cWDM_Monitor, "stop", RUBY_METHOD_FUNC(rb_monitor_stop), 0);
1
+ #include "wdm.h"
2
+
3
+ #include "utils.h"
4
+ #include "entry.h"
5
+ #include "queue.h"
6
+ #include "monitor.h"
7
+
8
+ #include "rb_change.h"
9
+ #include "rb_monitor.h"
10
+
11
+ // ----------------------------------------------------------
12
+ // Global variables
13
+ // ----------------------------------------------------------
14
+
15
+ VALUE cWDM_Monitor;
16
+
17
+ VALUE eWDM_UnknownFlagError;
18
+
19
+ // ----------------------------------------------------------
20
+ // Cached variables
21
+ // ----------------------------------------------------------
22
+
23
+ static ID wdm_rb_sym_call;
24
+ static ID wdm_rb_sym_files;
25
+ static ID wdm_rb_sym_directories;
26
+ static ID wdm_rb_sym_attributes;
27
+ static ID wdm_rb_sym_size;
28
+ static ID wdm_rb_sym_last_write;
29
+ static ID wdm_rb_sym_last_access;
30
+ static ID wdm_rb_sym_creation;
31
+ static ID wdm_rb_sym_security;
32
+ static ID wdm_rb_sym_default;
33
+
34
+ // ---------------------------------------------------------
35
+ // Prototypes of static functions
36
+ // ---------------------------------------------------------
37
+
38
+ static void monitor_mark(LPVOID);
39
+ static void monitor_free(LPVOID);
40
+ static VALUE rb_monitor_alloc(VALUE);
41
+
42
+ static DWORD id_to_flag(ID);
43
+ static DWORD extract_flags_from_rb_array(VALUE);
44
+ static VALUE combined_watch(BOOL, int, VALUE*, VALUE);
45
+ static VALUE rb_monitor_watch(int, VALUE*, VALUE);
46
+
47
+ static VALUE rb_monitor_watch_recursively(int, VALUE*, VALUE);
48
+
49
+ static void CALLBACK handle_entry_change(DWORD, DWORD, LPOVERLAPPED);
50
+ static BOOL register_monitoring_entry(WDM_PEntry);
51
+ static DWORD WINAPI start_monitoring(LPVOID);
52
+
53
+ static VALUE wait_for_changes(LPVOID);
54
+ static void process_changes(WDM_PQueue);
55
+ static void stop_monitoring(LPVOID);
56
+ static VALUE rb_monitor_run_bang(VALUE);
57
+
58
+ static VALUE rb_monitor_stop(VALUE);
59
+
60
+ // ----------------------------------------------------------
61
+
62
+ static void
63
+ monitor_mark(LPVOID param)
64
+ {
65
+ WDM_PMonitor monitor;
66
+ WDM_PEntry entry;
67
+
68
+ monitor = (WDM_PMonitor)param;
69
+ entry = monitor->head;
70
+
71
+ while(entry != NULL) {
72
+ rb_gc_mark(entry->user_data->callback);
73
+ entry = entry->next;
74
+ }
75
+ }
76
+
77
+ static void
78
+ monitor_free(LPVOID param)
79
+ {
80
+ WDM_PMonitor monitor;
81
+ WDM_PEntry entry;
82
+
83
+ WDM_DEBUG("Freeing a monitor object!");
84
+
85
+ monitor = (WDM_PMonitor)param;
86
+ entry = monitor->head;
87
+
88
+ stop_monitoring(monitor); // If the monitor is already stopped, it would do nothing
89
+
90
+ while(entry != NULL) {
91
+ if ( entry->event_container.hEvent != NULL ) {
92
+ wdm_monitor_callback_param_free(
93
+ (WDM_PMonitorCallbackParam)entry->event_container.hEvent
94
+ );
95
+ }
96
+ entry = entry->next;
97
+ }
98
+
99
+ wdm_monitor_free(monitor);
100
+ }
101
+
102
+ static VALUE
103
+ rb_monitor_alloc(VALUE self)
104
+ {
105
+ WDM_DEBUG("--------------------------------");
106
+ WDM_DEBUG("Allocating a new monitor object!");
107
+ WDM_DEBUG("--------------------------------");
108
+
109
+ return Data_Wrap_Struct(self, monitor_mark, monitor_free, wdm_monitor_new());
110
+ }
111
+
112
+ static DWORD
113
+ id_to_flag(ID id)
114
+ {
115
+ if ( id == wdm_rb_sym_default ) return WDM_MONITOR_FLAGS_DEFAULT;
116
+
117
+ // TODO: Maybe reorder the if's in the frequency of use for better performance?
118
+ if ( id == wdm_rb_sym_files ) return FILE_NOTIFY_CHANGE_FILE_NAME;
119
+ if ( id == wdm_rb_sym_directories ) return FILE_NOTIFY_CHANGE_DIR_NAME;
120
+ if ( id == wdm_rb_sym_attributes ) return FILE_NOTIFY_CHANGE_ATTRIBUTES;
121
+ if ( id == wdm_rb_sym_size ) return FILE_NOTIFY_CHANGE_SIZE;
122
+ if ( id == wdm_rb_sym_last_write ) return FILE_NOTIFY_CHANGE_LAST_WRITE;
123
+ if ( id == wdm_rb_sym_last_access ) return FILE_NOTIFY_CHANGE_LAST_ACCESS;
124
+ if ( id == wdm_rb_sym_creation ) return FILE_NOTIFY_CHANGE_CREATION;
125
+ if ( id == wdm_rb_sym_security ) return FILE_NOTIFY_CHANGE_SECURITY;
126
+
127
+ rb_raise(eWDM_UnknownFlagError, "Unknown watch flag: ':%s'", rb_id2name(id));
128
+ }
129
+
130
+ static DWORD
131
+ extract_flags_from_rb_array(VALUE flags_array)
132
+ {
133
+ VALUE flag_symbol;
134
+ DWORD flags;
135
+
136
+ flags = 0;
137
+
138
+ while ( RARRAY_LEN(flags_array) != 0 ) {
139
+ flag_symbol = rb_ary_pop(flags_array);
140
+ Check_Type(flag_symbol, T_SYMBOL);
141
+ flags |= id_to_flag( SYM2ID(flag_symbol) );
142
+ }
143
+
144
+ return flags;
145
+ }
146
+
147
+ static VALUE
148
+ combined_watch(BOOL recursively, int argc, VALUE *argv, VALUE self)
149
+ {
150
+ WDM_PMonitor monitor;
151
+ WDM_PEntry entry;
152
+ int directory_letters_count;
153
+ VALUE directory, flags, os_encoded_directory;
154
+ BOOL running;
155
+
156
+ // TODO: Maybe raise a more user-friendly error?
157
+ rb_need_block();
158
+
159
+ Data_Get_Struct(self, WDM_Monitor, monitor);
160
+
161
+ EnterCriticalSection(&monitor->lock);
162
+ running = monitor->running;
163
+ LeaveCriticalSection(&monitor->lock);
164
+
165
+ if ( running ) {
166
+ rb_raise(eWDM_MonitorRunningError, "You can't watch new directories while the monitor is running!");
167
+ }
168
+
169
+ rb_scan_args(argc, argv, "1*", &directory, &flags);
170
+
171
+ Check_Type(directory, T_STRING);
172
+
173
+ entry = wdm_entry_new();
174
+ entry->user_data->watch_childeren = recursively;
175
+ entry->user_data->callback = rb_block_proc();
176
+ entry->user_data->flags = RARRAY_LEN(flags) == 0 ? WDM_MONITOR_FLAGS_DEFAULT : extract_flags_from_rb_array(flags);
177
+
178
+ // WTF Ruby source: The original code (file.c) uses the following macro to make sure that the encoding
179
+ // of the string is ASCII-compatible, but UTF-16LE (Windows default encoding) is not!!!
180
+ //
181
+ // FilePathValue(directory);
182
+
183
+ os_encoded_directory = rb_str_encode_ospath(directory);
184
+
185
+ // RSTRING_LEN can't be used because it would return the count of bytes the string uses in its encoding (like UTF-8).
186
+ // UTF-8 might use more than one byte for the char, which is not needed for WCHAR strings.
187
+ // Also, the result of MultiByteToWideChar _includes_ the NULL char at the end, which is not true for RSTRING.
188
+ //
189
+ // Example: 'C:\Users\Maher\Desktop\تجربة' with __ENCODING__ == UTF-8
190
+ // MultiByteToWideChar => 29 (28-char + null)
191
+ // RSTRING_LEN => 33 (23-char + 10-bytes for 5 Arabic letters which take 2 bytes each)
192
+ //
193
+ directory_letters_count = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(os_encoded_directory), -1, NULL, 0);
194
+
195
+ entry->user_data->dir = ALLOCA_N(WCHAR, directory_letters_count);
196
+
197
+ MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(os_encoded_directory), -1, entry->user_data->dir, directory_letters_count);
198
+
199
+ WDM_WDEBUG("New path to watch: '%s'", entry->user_data->dir);
200
+
201
+ entry->user_data->dir = wdm_utils_full_pathname(entry->user_data->dir);
202
+
203
+ if ( entry->user_data->dir == 0 ) {
204
+ wdm_entry_free(entry);
205
+ rb_raise(eWDM_Error, "Can't get the absolute path for the passed directory: '%s'!", RSTRING_PTR(directory));
206
+ }
207
+
208
+ if ( ! wdm_utils_unicode_is_directory(entry->user_data->dir) ) {
209
+ wdm_entry_free(entry);
210
+ rb_raise(eWDM_InvalidDirectoryError, "No such directory: '%s'!", RSTRING_PTR(directory));
211
+ }
212
+
213
+ entry->dir_handle = CreateFileW(
214
+ entry->user_data->dir, // pointer to the file name
215
+ FILE_LIST_DIRECTORY, // access (read/write) mode
216
+ FILE_SHARE_READ // share mode
217
+ | FILE_SHARE_WRITE
218
+ | FILE_SHARE_DELETE,
219
+ NULL, // security descriptor
220
+ OPEN_EXISTING, // how to create
221
+ FILE_FLAG_BACKUP_SEMANTICS
222
+ | FILE_FLAG_OVERLAPPED, // file attributes
223
+ NULL
224
+ );
225
+
226
+ if ( entry->dir_handle == INVALID_HANDLE_VALUE ) {
227
+ wdm_entry_free(entry);
228
+ rb_raise(eWDM_Error, "Can't watch directory: '%s'!", RSTRING_PTR(directory));
229
+ }
230
+
231
+ // Store a reference to the entry instead of an event as the event
232
+ // won't be used when using callbacks.
233
+ entry->event_container.hEvent = wdm_monitor_callback_param_new(monitor, entry);
234
+
235
+ wdm_monitor_update_head(monitor, entry);
236
+
237
+ WDM_WDEBUG("Watching directory: '%s'", entry->user_data->dir);
238
+
239
+ return Qnil;
240
+ }
241
+
242
+ static VALUE
243
+ rb_monitor_watch(int argc, VALUE *argv, VALUE self)
244
+ {
245
+ return combined_watch(FALSE, argc, argv, self);
246
+ }
247
+
248
+ static VALUE
249
+ rb_monitor_watch_recursively(int argc, VALUE *argv, VALUE self)
250
+ {
251
+ return combined_watch(TRUE, argc, argv, self);
252
+ }
253
+
254
+ static void CALLBACK
255
+ handle_entry_change(
256
+ DWORD err_code, // completion code
257
+ DWORD bytes_transfered, // number of bytes transferred
258
+ LPOVERLAPPED event_container // I/O information buffer
259
+ ) {
260
+ WDM_PMonitorCallbackParam param;
261
+ WDM_PQueueItem data_to_process;
262
+
263
+ if ( err_code == ERROR_OPERATION_ABORTED ) {
264
+ // Async operation was canceled. This shouldn't happen.
265
+ // TODO:
266
+ // 1. Maybe add a union in the queue for errors?
267
+ // 2. What's the best action when this happens?
268
+ WDM_DEBUG("Dir handler closed in the process callback!");
269
+ return;
270
+ }
271
+
272
+ if ( ! bytes_transfered ) {
273
+ WDM_DEBUG("Buffer overflow?! Changes are bigger than the buffer!");
274
+ return;
275
+ }
276
+
277
+ param = (WDM_PMonitorCallbackParam)event_container->hEvent;
278
+ data_to_process = wdm_queue_item_new(WDM_QUEUE_ITEM_TYPE_DATA);
279
+ data_to_process->data = wdm_queue_item_data_new();
280
+
281
+ WDM_WDEBUG("Change detected in '%s'", param->entry->user_data->dir);
282
+
283
+ data_to_process->data->user_data = param->entry->user_data;
284
+
285
+ // Copy change data to the backup buffer
286
+ memcpy(data_to_process->data->buffer, param->entry->buffer, bytes_transfered);
287
+
288
+ // Add the backup buffer to the change queue
289
+ wdm_queue_enqueue(param->monitor->changes, data_to_process);
290
+
291
+ // Resume watching the dir for changes
292
+ register_monitoring_entry(param->entry);
293
+
294
+ // Tell the processing thread to process the changes
295
+ if ( WaitForSingleObject(param->monitor->process_event, 0) != WAIT_OBJECT_0 ) { // Check if already signaled
296
+ SetEvent(param->monitor->process_event);
297
+ }
298
+ }
299
+
300
+ static BOOL
301
+ register_monitoring_entry(WDM_PEntry entry)
302
+ {
303
+ BOOL success;
304
+ DWORD bytes;
305
+ bytes = 0; // Not used because the process callback gets passed the written bytes
306
+
307
+ success = ReadDirectoryChangesW(
308
+ entry->dir_handle, // handle to directory
309
+ entry->buffer, // read results buffer
310
+ WDM_BUFFER_SIZE, // length of buffer
311
+ entry->user_data->watch_childeren, // monitoring option
312
+ entry->user_data->flags, // filter conditions
313
+ &bytes, // bytes returned
314
+ &entry->event_container, // overlapped buffer
315
+ &handle_entry_change // process callback
316
+ );
317
+
318
+ if ( ! success ) {
319
+ WDM_DEBUG("ReadDirectoryChangesW failed with error (%d): %s", GetLastError(), rb_w32_strerror(GetLastError()));
320
+ return FALSE;
321
+ }
322
+
323
+ return TRUE;
324
+ }
325
+
326
+ static DWORD WINAPI
327
+ start_monitoring(LPVOID param)
328
+ {
329
+ WDM_PMonitor monitor;
330
+ WDM_PEntry curr_entry;
331
+
332
+ monitor = (WDM_PMonitor)param;
333
+ curr_entry = monitor->head;
334
+
335
+ WDM_DEBUG("Starting the monitoring thread!");
336
+
337
+ while(curr_entry != NULL) {
338
+ if ( ! register_monitoring_entry(curr_entry) ) {
339
+ WDM_PQueueItem error_item;
340
+ int directory_bytes;
341
+ LPSTR multibyte_directory;
342
+
343
+ directory_bytes = WideCharToMultiByte(CP_UTF8, 0, curr_entry->user_data->dir, -1, NULL, 0, NULL, NULL);
344
+ multibyte_directory = ALLOCA_N(CHAR, directory_bytes);
345
+ WideCharToMultiByte(CP_UTF8, 0, curr_entry->user_data->dir, -1, multibyte_directory, directory_bytes, NULL, NULL);
346
+
347
+ error_item = wdm_queue_item_new(WDM_QUEUE_ITEM_TYPE_ERROR);
348
+ error_item->error = wdm_queue_item_error_new(
349
+ eWDM_UnwatchableDirectoryError, "Can't watch directory: '%s'!", multibyte_directory
350
+ );
351
+
352
+ wdm_queue_enqueue(monitor->changes, error_item);
353
+ SetEvent(monitor->process_event);
354
+ }
355
+
356
+ curr_entry = curr_entry->next;
357
+ }
358
+
359
+ while(monitor->running) {
360
+ // TODO: Is this the best way to do it?
361
+ if ( WaitForSingleObjectEx(monitor->stop_event, INFINITE, TRUE) == WAIT_OBJECT_0) {
362
+ WDM_DEBUG("Exiting the monitoring thread!");
363
+ ExitThread(0);
364
+ }
365
+ }
366
+
367
+ return 0;
368
+ }
369
+
370
+ static VALUE
371
+ wait_for_changes(LPVOID param)
372
+ {
373
+ HANDLE process_event;
374
+
375
+ process_event = (HANDLE)param;
376
+
377
+ return WaitForSingleObject(process_event, INFINITE) == WAIT_OBJECT_0 ? Qtrue : Qfalse;
378
+ }
379
+
380
+ static void
381
+ process_changes(WDM_PQueue changes)
382
+ {
383
+ WDM_PQueueItem item;
384
+ LPBYTE current_info_entry_offset;
385
+ PFILE_NOTIFY_INFORMATION info;
386
+ VALUE event;
387
+
388
+ WDM_DEBUG("---------------------------");
389
+ WDM_DEBUG("Process changes");
390
+ WDM_DEBUG("--------------------------");
391
+
392
+ while( ! wdm_queue_is_empty(changes) ) {
393
+ item = wdm_queue_dequeue(changes);
394
+
395
+ if ( item->type == WDM_QUEUE_ITEM_TYPE_ERROR ) {
396
+ rb_raise(item->error->exception_klass, item->error->message);
397
+ }
398
+ else {
399
+ current_info_entry_offset = (LPBYTE)item->data->buffer;
400
+
401
+ for(;;) {
402
+ info = (PFILE_NOTIFY_INFORMATION)current_info_entry_offset;
403
+ event = wdm_rb_change_new_from_notification(item->data->user_data->dir, info);
404
+
405
+ WDM_DEBUG("---------------------------");
406
+ WDM_DEBUG("Running user callback");
407
+ WDM_DEBUG("--------------------------");
408
+
409
+ rb_funcall(item->data->user_data->callback, wdm_rb_sym_call, 1, event);
410
+
411
+ WDM_DEBUG("---------------------------");
412
+
413
+ if ( ! info->NextEntryOffset ) break;
414
+
415
+ current_info_entry_offset += info->NextEntryOffset;
416
+ }
417
+ }
418
+
419
+ wdm_queue_item_free(item);
420
+ }
421
+ }
422
+
423
+ static void
424
+ stop_monitoring(LPVOID param)
425
+ {
426
+ BOOL already_stopped;
427
+ WDM_PMonitor monitor;
428
+ WDM_PEntry entry;
429
+
430
+ monitor = (WDM_PMonitor)param;
431
+ already_stopped = FALSE;
432
+
433
+ WDM_DEBUG("Stopping the monitor!");
434
+
435
+ EnterCriticalSection(&monitor->lock);
436
+ if ( ! monitor->running ) {
437
+ already_stopped = TRUE;
438
+ }
439
+ else {
440
+ monitor->running = FALSE;
441
+ }
442
+ LeaveCriticalSection(&monitor->lock);
443
+
444
+ if (already_stopped) {
445
+ WDM_DEBUG("Can't stop monitoring because it's already stopped (or it's never been started)!!");
446
+ return;
447
+ }
448
+
449
+ entry = monitor->head;
450
+
451
+ while(entry != NULL) {
452
+ CancelIo(entry->dir_handle); // Stop monitoring changes
453
+ entry = entry->next;
454
+ }
455
+
456
+ SetEvent(monitor->stop_event);
457
+ SetEvent(monitor->process_event); // The process code checks after the wait for an exit signal
458
+ WaitForSingleObject(monitor->monitoring_thread, 10000);
459
+ }
460
+
461
+ static VALUE
462
+ rb_monitor_run_bang(VALUE self)
463
+ {
464
+ BOOL already_running,
465
+ waiting_succeeded;
466
+ WDM_PMonitor monitor;
467
+
468
+ WDM_DEBUG("Running the monitor!");
469
+
470
+ Data_Get_Struct(self, WDM_Monitor, monitor);
471
+ already_running = FALSE;
472
+
473
+ EnterCriticalSection(&monitor->lock);
474
+ if ( monitor->running ) {
475
+ already_running = TRUE;
476
+ }
477
+ else {
478
+ monitor->running = TRUE;
479
+ }
480
+ LeaveCriticalSection(&monitor->lock);
481
+
482
+ if (already_running) {
483
+ WDM_DEBUG("Not doing anything because the monitor is already running!");
484
+ return Qnil;
485
+ }
486
+
487
+ // Reset events
488
+ ResetEvent(monitor->process_event);
489
+ ResetEvent(monitor->stop_event);
490
+
491
+ monitor->monitoring_thread = CreateThread(
492
+ NULL, // default security attributes
493
+ 0, // use default stack size
494
+ start_monitoring, // thread function name
495
+ monitor, // argument to thread function
496
+ 0, // use default creation flags
497
+ NULL // Ignore thread identifier
498
+ );
499
+
500
+ if ( monitor->monitoring_thread == NULL ) {
501
+ rb_raise(eWDM_Error, "Can't create a thread for the monitor!");
502
+ }
503
+
504
+ while ( monitor->running ) {
505
+
506
+ // Ruby 2.2 removed the 'rb_thread_blocking_region' function. Hence, we now need
507
+ // to check if the replacement function is defined and use it if it's available.
508
+ #ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL
509
+ waiting_succeeded = rb_thread_call_without_gvl(wait_for_changes, monitor->process_event, stop_monitoring, monitor);
510
+ #else
511
+ waiting_succeeded = rb_thread_blocking_region(wait_for_changes, monitor->process_event, stop_monitoring, monitor);
512
+ #endif
513
+
514
+ if ( waiting_succeeded == Qfalse ) {
515
+ rb_raise(eWDM_Error, "Failed while waiting for a change in the watched directories!");
516
+ }
517
+
518
+ if ( ! monitor->running ) {
519
+ wdm_queue_empty(monitor->changes);
520
+ return Qnil;
521
+ }
522
+
523
+ process_changes(monitor->changes);
524
+
525
+ if ( ! ResetEvent(monitor->process_event) ) {
526
+ rb_raise(eWDM_Error, "Couldn't reset system events to watch for changes!");
527
+ }
528
+ }
529
+
530
+ return Qnil;
531
+ }
532
+
533
+ static VALUE
534
+ rb_monitor_stop(VALUE self)
535
+ {
536
+ WDM_PMonitor monitor;
537
+
538
+ Data_Get_Struct(self, WDM_Monitor, monitor);
539
+
540
+ stop_monitoring(monitor);
541
+
542
+ WDM_DEBUG("Stopped the monitor!");
543
+
544
+ return Qnil;
545
+ }
546
+
547
+ void
548
+ wdm_rb_monitor_init()
549
+ {
550
+ WDM_DEBUG("Registering WDM::Monitor with Ruby!");
551
+
552
+ wdm_rb_sym_call = rb_intern("call");
553
+ wdm_rb_sym_files = rb_intern("files");
554
+ wdm_rb_sym_directories = rb_intern("directories");
555
+ wdm_rb_sym_attributes = rb_intern("attributes");
556
+ wdm_rb_sym_size = rb_intern("size");
557
+ wdm_rb_sym_last_write = rb_intern("last_write");
558
+ wdm_rb_sym_last_access = rb_intern("last_access");
559
+ wdm_rb_sym_creation = rb_intern("creation");
560
+ wdm_rb_sym_security = rb_intern("security");
561
+ wdm_rb_sym_default = rb_intern("default");
562
+
563
+ eWDM_MonitorRunningError = rb_define_class_under(mWDM, "MonitorRunningError", eWDM_Error);
564
+ eWDM_InvalidDirectoryError = rb_define_class_under(mWDM, "InvalidDirectoryError", eWDM_Error);
565
+ eWDM_UnknownFlagError = rb_define_class_under(mWDM, "UnknownFlagError", eWDM_Error);
566
+ eWDM_UnwatchableDirectoryError = rb_define_class_under(mWDM, "UnwatchableDirectoryError", eWDM_Error);
567
+
568
+ cWDM_Monitor = rb_define_class_under(mWDM, "Monitor", rb_cObject);
569
+
570
+ rb_define_alloc_func(cWDM_Monitor, rb_monitor_alloc);
571
+ rb_define_method(cWDM_Monitor, "watch", RUBY_METHOD_FUNC(rb_monitor_watch), -1);
572
+ rb_define_method(cWDM_Monitor, "watch_recursively", RUBY_METHOD_FUNC(rb_monitor_watch_recursively), -1);
573
+ rb_define_method(cWDM_Monitor, "run!", RUBY_METHOD_FUNC(rb_monitor_run_bang), 0);
574
+ rb_define_method(cWDM_Monitor, "stop", RUBY_METHOD_FUNC(rb_monitor_stop), 0);
571
575
  }