Class: Ignis::Shared::MemoryContract

Inherits:
Object
  • Object
show all
Defined in:
lib/nnw/shared/memory_contract.rb

Overview

MemoryContract — Pinned memory ownership enforcement.

Prevents double-free and use-after-free — the actual bugs that killed the previous Ignis build.

Rules:

- An NvArray may only be freed by its current owner.
- Ignis is the default owner after allocation.
- NvCCL must call acquire before operating, release when done.
- WNAIS must call acquire before NOVA I/O, release when done.
- Acquiring while ref_count > 1 raises MemoryContractViolation.
- Pinned host memory is always owned by Ignis::Memory. NvCCL may
  read it but may not free it.

Thread-safe: all operations protected by Monitor.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeMemoryContract

Instance methods



99
100
101
102
# File 'lib/nnw/shared/memory_contract.rb', line 99

def initialize
  @monitor = Monitor.new
  @tracked = {} # id -> {array:, tracked_at:}
end

Class Method Details

.acquire(array, by:) ⇒ Boolean

Acquire ownership of an NvArray.

Transfers ownership from the current owner to the requesting layer. Fails if ref_count > 1 (concurrent shared references prevent safe transfer).

Parameters:

  • array (NvArray)

    the array to acquire

  • by (Symbol)

    the requesting owner (:nvruby, :nvccl, or :wnais)

Returns:

  • (Boolean)

    true if acquisition succeeded

Raises:



41
42
43
# File 'lib/nnw/shared/memory_contract.rb', line 41

def self.acquire(array, by:)
  instance.acquire(array, by: by)
end

.assert_owner!(array, expected_owner) ⇒ void

This method returns an undefined value.

Assert that an NvArray is owned by the expected owner.

Parameters:

  • array (NvArray)

    the array to check

  • expected_owner (Symbol)

    the expected owner

Raises:



61
62
63
# File 'lib/nnw/shared/memory_contract.rb', line 61

def self.assert_owner!(array, expected_owner)
  instance.assert_owner!(array, expected_owner)
end

.auditArray<Hash>

Audit all tracked arrays for potential leaks.

Returns information about all live arrays that have been tracked and are older than 5 seconds.

Returns:

  • (Array<Hash>)

    array of owner:, age_ms:, shape:, dtype:



71
72
73
# File 'lib/nnw/shared/memory_contract.rb', line 71

def self.audit
  instance.audit
end

.instanceMemoryContract

Returns singleton instance.

Returns:



22
23
24
# File 'lib/nnw/shared/memory_contract.rb', line 22

def self.instance
  @instance ||= new
end

.release(array, by:) ⇒ Boolean

Release ownership of an NvArray back to the default owner (:nvruby).

Parameters:

  • array (NvArray)

    the array to release

  • by (Symbol)

    the current owner releasing the array

Returns:

  • (Boolean)

    true if release succeeded

Raises:



51
52
53
# File 'lib/nnw/shared/memory_contract.rb', line 51

def self.release(array, by:)
  instance.release(array, by: by)
end

.reset!void

This method returns an undefined value.

Reset the singleton instance (for testing only).



28
29
30
# File 'lib/nnw/shared/memory_contract.rb', line 28

def self.reset!
  @instance = new
end

.track(array) ⇒ void

This method returns an undefined value.

Track an NvArray in the contract system.

Parameters:



79
80
81
# File 'lib/nnw/shared/memory_contract.rb', line 79

def self.track(array)
  instance.track(array)
end

.tracked_countInteger

Get the number of tracked arrays.

Returns:

  • (Integer)


93
94
95
# File 'lib/nnw/shared/memory_contract.rb', line 93

def self.tracked_count
  instance.tracked_count
end

.untrack(array) ⇒ void

This method returns an undefined value.

Remove an NvArray from tracking (called after free).

Parameters:



87
88
89
# File 'lib/nnw/shared/memory_contract.rb', line 87

def self.untrack(array)
  instance.untrack(array)
end

Instance Method Details

#acquire(array, by:) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/nnw/shared/memory_contract.rb', line 104

def acquire(array, by:)
  validate_owner!(by)

  @monitor.synchronize do
    current_owner = array.owner

    if current_owner == by
      raise MemoryContractViolation,
            "NvArray##{array.id} is already owned by #{by.inspect} — double-acquire"
    end

    array.transfer_ownership(by)
    track_if_needed(array)
    true
  end
end

#assert_owner!(array, expected_owner) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
# File 'lib/nnw/shared/memory_contract.rb', line 136

def assert_owner!(array, expected_owner)
  validate_owner!(expected_owner)

  actual_owner = @monitor.synchronize { array.owner }

  unless actual_owner == expected_owner
    raise MemoryContractViolation,
          "NvArray##{array.id} expected owner #{expected_owner.inspect}, " \
          "actual #{actual_owner.inspect}"
  end
end

#auditObject



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/nnw/shared/memory_contract.rb', line 148

def audit
  now = Time.now
  threshold_seconds = 5.0

  @monitor.synchronize do
    @tracked.values
            .select { |entry| (now - entry[:tracked_at]) > threshold_seconds }
            .map do |entry|
              arr = entry[:array]
              {
                array_id: arr.id,
                owner: arr.owner,
                age_ms: ((now - entry[:tracked_at]) * 1000).round,
                shape: arr.shape,
                dtype: arr.dtype,
                freed: arr.freed?
              }
            end
  end
end

#release(array, by:) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/nnw/shared/memory_contract.rb', line 121

def release(array, by:)
  validate_owner!(by)

  @monitor.synchronize do
    unless array.owner == by
      raise MemoryContractViolation,
            "NvArray##{array.id} is owned by #{array.owner.inspect}, " \
            "not #{by.inspect} — cannot release"
    end

    array.transfer_ownership(:nvruby)
    true
  end
end

#track(array) ⇒ Object



169
170
171
172
173
# File 'lib/nnw/shared/memory_contract.rb', line 169

def track(array)
  @monitor.synchronize do
    @tracked[array.id] = { array: array, tracked_at: Time.now }
  end
end

#tracked_countObject



181
182
183
# File 'lib/nnw/shared/memory_contract.rb', line 181

def tracked_count
  @monitor.synchronize { @tracked.size }
end

#untrack(array) ⇒ Object



175
176
177
178
179
# File 'lib/nnw/shared/memory_contract.rb', line 175

def untrack(array)
  @monitor.synchronize do
    @tracked.delete(array.id)
  end
end