class

MFile

Inherits IO < Reference < Object

Memory mapped file If no capacity is given the file is open in read only mode and capacity is set to the current file size. If the process crashes the file will be capacity large, not size large, only on graceful close is the file truncated to its size. The file does not expand further than initial capacity, unless manually expanded.

Constants

DONTNEED_CHUNK_SIZE = PMD_SIZE - PAGE_SIZE

madvise(MADV_DONTNEED) over PMD_SIZE or more lets the kernel reclaim the emptied page table, and that reclaim flushes the TLB at the wrong address on Linux 7.0.0-rc1 through 7.1.8 (CVE-2026-74674). Whatever maps that address next then faults in a loop. The check is >=, so one page under is the largest safe call. Only DontNeed zaps PTEs; the other advices set VMA flags, where chunking would needlessly split the VMA.

PMD_SIZE = PAGE_SIZE.to_i64 * (PAGE_SIZE // 8)

One PTE page table maps PAGE_SIZE / sizeof(pte_t) entries, 8 bytes each on 64-bit: 2 MiB with 4K pages, 512 MiB with 64K pages.

Constructors

new(path : String, capacity : Int | Nil = nil, writeonly : Bool = false)

Map a file, if no capacity is given the file must exists and the file will be mapped as readonly The file won't be truncated if the capacity is smaller than current size

Source

Class methods

mmap_count
Source
open(path, capacity : Int | Nil = nil, writeonly = false, & : self -> _)
Source

Instance methods

advise(advice : Advice, addr = @buffer, length = @capacity) : Nil
Source
allocated_size

Bytes the file occupies on disk as of open. A segment a crash left at its capacity is sparse past its data, and reading the hole would only zero-fill page cache

Source
capacity
Source
clear_needs_msync!
Source
close(truncate_to_size = true)

The file will be truncated to its size unless readonly or deleted. A leased file (see #lease) is unmapped when the last lease is released instead. That unmap doesn't truncate, as the file may have been opened again by then, so a leased file is truncated right away. The flag is set before the lease count is read, and release_lease decrements before reading the flag, so at least one of them sees the other and unmaps (close_mapping is idempotent).

Source
closed?

Returns true if this IO is closed.

IO defines returns false, but including types may override.

Source
copy_file_range(src : File, limit : Int)
Source
copy_file_range(src : MFile, limit : Int)
Source
delete(*, raise_on_missing = true) : Nil
Source
deleted?
Source
dontneed
Source
flush

Flushes buffered data, if any.

IO defines this is a no-op method, but including types may override.

Source
fsync

Block until the written pages are on disk. A file closed in the meantime still has its dirty pages in the page cache, so fsync it by path instead, unless it's deleted and nothing needs to be persisted.

Source
inspect(io : IO)

Appends a String representation of this object which includes its class name, its object address and the values of all instance variables.

class Person
  def initialize(@name : String, @age : Int32)
  end
end

Person.new("John", 32).inspect # => #<Person:0x10fd31f20 @name="John", @age=32>
Source
lease

Keeps the mapping open until #release_lease, even if #close is called meanwhile. Must not be called after #close.

Source
mark_needs_msync!

Returns whether the file already was marked

Source
path
Source
pos

Returns the current position (in bytes) in this IO.

The IO class raises on this method, but some subclasses, notable IO::FileDescriptor and IO::Memory implement it.

File.write("testfile", "hello")

file = File.new("testfile")
file.pos     # => 0
file.gets(2) # => "he"
file.pos     # => 2
Source
pos=(pos)

Sets the current position (in bytes) in this IO.

The IO class raises on this method, but some subclasses, notable IO::FileDescriptor and IO::Memory implement it.

File.write("testfile", "hello")

file = File.new("testfile")
file.pos = 3
file.gets_to_end # => "lo"
Source
read(slice : Bytes)

Reads at most slice.size bytes from this IO into slice. Returns the number of bytes read, which is 0 if and only if there is no more data to read (so checking for 0 is the way to detect end of file).

io = IO::Memory.new "hello"
slice = Bytes.new(4)
io.read(slice) # => 4
slice          # => Bytes[104, 101, 108, 108]
io.read(slice) # => 1
slice          # => Bytes[111, 101, 108, 108]
io.read(slice) # => 0
Source
read_at(pos, slice)

Read from a specific position in the file

Source
read_byte

Reads a single byte from this IO. Returns nil if there is no more data to read.

io = IO::Memory.new "a"
io.read_byte # => 97
io.read_byte # => nil
Source
release_lease
Source
rename(new_path : String) : Nil
Source
resize(new_size : Int) : Nil

Resize the file, so that read operations can't happen beyond new_size this does not change the actual file size on disk and you can still write beyond new_size

Source
rewind

Rewinds this IO. By default this method raises, but including types may implement it.

Source
seek(offset : Int, whence : IO::Seek = IO::Seek::Set)
Source
size
Source
skip(bytes_count : Int) : Int

Reads and discards exactly bytes_count bytes. Raises IO::EOFError if there aren't at least bytes_count bytes.

io = IO::Memory.new "hello world"
io.skip(6)
io.gets    # => "world"
io.skip(1) # raises IO::EOFError
Source
take_created!

Returns true once if this MFile created the file on disk

Source
to_slice(pos, size)
Source
to_slice
Source
truncate(new_capacity) : Nil

Truncate the file to the given capacity (contracting only, no expansion) The truncated part is unmapped from memory

Source
write(slice : Bytes) : Nil

Append only

Source

Nested types