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_CREATE needs ≥ 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 of Inotify::Watcher is protected by a Sync::Mutex, so watch, unwatch, watching, on_event, clear_event_handlers and close are 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: true to run the event-reading fiber on a dedicated Fiber::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 against IN_Q_OVERFLOW — at the cost of one thread per watcher. Events cross context boundaries through a context-safe Channel, 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

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

License

MIT

Repository

crystal-inotify

Owner
Statistic
  • 0
  • 0
  • 0
  • 0
  • 0
  • about 1 month ago
  • September 4, 2026
License

MIT License

Links
Synced at

Sun, 13 Sep 2026 16:00:43 GMT

Languages