crystal-inotify
inotify
Linux inotify bindings for the Crystal programming language. Watch files and directories for filesystem events.
- Linux only (kernel ≥ 2.6.13;
IN_MASK_CREATEneeds ≥ 4.18) - Crystal ≥ 1.21.0
- Fiber-based: events are read on a dedicated fiber that suspends on Crystal's event loop — no busy polling, no extra threads
- API-compatible with
petoem/inotify.cr
Installation
Add this to your application's shard.yml:
dependencies:
crystal-inotify:
github: shpeckman/crystal-inotify
Then run:
shards install
Usage
require "crystal-inotify"
watcher = Inotify.watch("/path/to/file.txt", isolated: true) do |event|
puts "#{event.type}: #{event.full_path}"
end
sleep 10.seconds
watcher.close
Note: You have to keep the main fiber busy (e.g. with
sleep), or your program will exit.
Watcher API
watcher = Inotify::Watcher.new(recursive: true)
watcher.on_event { |event| puts "first handler: #{event}" }
watcher.on_event { |event| puts "second handler: #{event}" }
watcher.clear_event_handlers
watcher.watch("/path/to/dir")
watcher.watch("/path/to/other", mask: LibInotify::IN_MODIFY | LibInotify::IN_CLOSE_WRITE)
watcher.watching
watcher.unwatch("/path/to/other")
watcher.closed?
watcher.close
With recursive: true, all existing subdirectories are watched, and subdirectories created or moved in later are watched automatically.
Events
Every Inotify::Event has:
| Method | Type | Description |
|---|---|---|
#type |
Inotify::Event::Type |
The event type (CREATE, MODIFY, MOVED_FROM, ...) |
#name |
String? |
Name of the file inside the watched directory (nil for file watches) |
#path |
String? |
The watched path the event occurred against |
#full_path |
String? |
path joined with name |
#mask |
UInt32 |
Raw event bitmask |
#cookie |
UInt32 |
Connects related events (MOVED_FROM ↔ MOVED_TO) |
#wd |
Int32 |
Watch descriptor |
#directory? |
Bool |
Whether the event occurred against a directory |
#type_is?(bits) |
Bool |
Test raw LibInotify::IN_* bits against #mask |
Supported Event Types
The #type method returns an Inotify::Event::Type enum matching the following kernel events:
| Type | Description |
|---|---|
ACCESS |
File was accessed. |
MODIFY |
File was modified. |
ATTRIB |
Metadata changed. |
CLOSE_WRITE |
Writable file was closed. |
CLOSE_NOWRITE |
Unwritable file was closed. |
OPEN |
File was opened. |
MOVED_FROM |
File was moved from X. |
MOVED_TO |
File was moved to Y. |
CREATE |
Subfile was created. |
DELETE |
Subfile was deleted. |
DELETE_SELF |
Self was deleted. |
MOVE_SELF |
Self was moved. |
CLOSE |
Helper: Close (either kind, CLOSE_WRITE, CLOSE_NOWRITE). |
MOVE |
Helper: Moves (either direction, MOVED_FROM, MOVED_TO). |
UNMOUNT |
Backing filesystem was unmounted. |
Q_OVERFLOW |
Event queue overflowed. |
IGNORED |
Watch was removed. |
UNKNOWN |
Fallback for unmapped bitmasks. |
The default mask is Inotify::DEFAULT_WATCH_FLAG (moves, modify, create, delete). Use LibInotify::IN_ALL_EVENTS to receive everything.
Error Handling
If an underlying inotify syscall fails, an Inotify::Error is raised. This inherits from Exception and includes SystemError, providing access to the underlying errno.
Concurrency notes (Crystal ≥ 1.21)
Crystal 1.21 enables execution contexts by default, and this shard embraces them:
- Thread-safe by
Sync. All mutable state ofInotify::Watcheris protected by aSync::Mutex, sowatch,unwatch,watching,on_event,clear_event_handlersandcloseare safe to call from fibers running in different — even parallel — execution contexts. The callback list is copy-on-write, so event dispatch never holds the lock while running your code (a callback can safely register more callbacks). - Optional isolated reader. Pass
isolated: trueto run the event-reading fiber on a dedicatedFiber::ExecutionContext::Isolated(its own OS thread). The kernel event queue keeps being drained even when your default context is busy with CPU-bound fibers that never yield — protecting againstIN_Q_OVERFLOW— at the cost of one thread per watcher. Events cross context boundaries through a context-safeChannel, and your callbacks still run on the execution context that created the watcher.
watcher = Inotify.watch("/var/log", recursive: true, isolated: true) do |event|
puts event.full_path
end
Example
A ready-to-run command line watcher is included:
crystal run examples/watch.cr -- /path/to/dir --recursive
Development
crystal spec
crystal tool format
To enable debug logging, configure the inotify log source, e.g.:
Log.setup("inotify", :debug)
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
License
crystal-inotify
- 0
- 0
- 0
- 0
- 0
- about 1 month ago
- September 4, 2026
MIT License
Sun, 13 Sep 2026 16:00:43 GMT