Class: Farce::WeakAtom

Inherits:
Abstract::WeakAtom show all
Includes:
Shareable::Delegated
Defined in:
lib/farce/weak_atom.rb

Overview

A Ractor-shareable atomic reference that retains its value weakly. Values support :raise (the default), :make_shareable, and :dedup. The value becomes nil after collection when no strong references remain. With :dedup, retain the canonical result returned by store or update. Keeping only the input alive may not keep the stored result alive. Assignment evaluates to the input rather than the canonical result. Comparisons observe the stored object without preparing their operands.

Examples:

Publishing the original object without keeping it alive

value = []
atom = Farce::WeakAtom.new(value, mode: :make_shareable)
atom.value.equal?(value) # => true
value = nil
# After collection, atom.value returns nil.

Instance Method Summary collapse

Methods included from Shareable

#ractor_shareable?

Methods inherited from Abstract::Atom

#blank?, #compare_by_identity?, #get, #value, #value=, #wait_until, #wait_until_match, #wait_until_non_nil, #wait_until_value, #wait_while, #wait_while_match, #wait_while_value

Methods included from Internal::ValueSerialization

#as_extended_json, #as_json, #to_bson, #to_bson_normalized_value, #to_cbor, #to_json, #to_msgpack

Methods included from Abstract::Value

#blank?, #unwrap, #value

Methods included from Internal::Copyable

#duplicable?

Constructor Details

#initialize(value = nil, mode: :raise, compare_by_identity: false) ⇒ WeakAtom

Returns a new instance of WeakAtom.

Parameters:

  • value (BasicObject, nil) (defaults to: nil) —

    the initial value

  • mode (Symbol) (defaults to: :raise) —

    :raise, :make_shareable, or :dedup

  • compare_by_identity (Boolean) (defaults to: false) —

    whether comparisons use object identity



29
30
31
32
# File 'lib/farce/weak_atom.rb', line 29

def initialize(value = nil, mode: :raise, compare_by_identity: false)
  @manager = Internal::WeakModeManager.new(mode:)
  super(@manager.wrap(value), compare_by_identity:)
end

Instance Method Details

#compare_and_set(expected, replacement, mode: nil, timeout: nil) ⇒ Boolean

Atomically replace the current value if it matches the expected value.

Parameters:

  • expected (BasicObject, nil) —

    the value to compare with the current value

  • new_value (BasicObject, nil) —

    the replacement value

  • timeout (Numeric, nil) (defaults to: nil) —

    the maximum number of seconds to wait

  • mode (Symbol, nil) (defaults to: nil) —

    the replacement's mode, or nil for the default

Returns:

  • (Boolean) —

    whether the value was replaced



90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/farce/weak_atom.rb', line 90

def compare_and_set(expected, replacement, mode: nil, timeout: nil)
  @manager.validate_mode!(mode)
  matched = false
  @atom.update(timeout:) do |current|
    next current unless values_equal?(current, expected)
    check_frozen!
    prepared = @manager.wrap(replacement, mode:)
    matched = true
    prepared
  end
  matched
end

#mode ⇒ Symbol

The default mode used to prepare replacement values.

Returns:

  • (Symbol)


36
# File 'lib/farce/weak_atom.rb', line 36

def mode = @manager.mode

#store(new_value, mode: nil, timeout: nil) { ... } ⇒ BasicObject?

Store a new value, waiting for any update in progress.

Parameters:

  • new_value (BasicObject, nil) —

    the new value

  • timeout (Numeric, nil) (defaults to: nil) —

    the maximum number of seconds to wait

  • mode (Symbol, nil) (defaults to: nil) —

    the replacement's mode, or nil for the default

Yields:

  • called when the timeout expires

Returns:

  • (BasicObject, nil) —

    the stored value or the fallback result



40
41
42
43
# File 'lib/farce/weak_atom.rb', line 40

def store(new_value, mode: nil, timeout: nil, &)
  check_frozen!
  super(@manager.wrap(new_value, mode:), timeout:, &)
end

#store_if_absent(mode: nil, timeout: nil) { ... } ⇒ BasicObject?

Compute and store a value if the current value is nil.

Parameters:

  • timeout (Numeric, nil) (defaults to: nil) —

    the maximum number of seconds to wait

  • mode (Symbol, nil) (defaults to: nil) —

    the result's mode, or nil for the default

Yields:

  • computes the value to store when the current value is nil

Yield Returns:

  • (BasicObject, nil) —

    the value to store

Returns:

  • (BasicObject, nil) —

    the current or newly stored value, or nil when the timeout expires



54
55
56
57
58
59
60
61
62
# File 'lib/farce/weak_atom.rb', line 54

def store_if_absent(mode: nil, timeout: nil)
  raise LocalJumpError, "no block given" unless block_given?
  @manager.validate_mode!(mode)
  super(timeout:) do
    value = yield
    check_frozen!
    @manager.wrap(value, mode:)
  end
end

#swap(new_value, mode: nil, timeout: nil) { ... } ⇒ BasicObject?

Replace the current value and return the previous value.

Parameters:

  • new_value (BasicObject, nil) —

    the new value

  • timeout (Numeric, nil) (defaults to: nil) —

    the maximum number of seconds to wait

  • mode (Symbol, nil) (defaults to: nil) —

    the replacement's mode, or nil for the default

Yields:

  • called when the timeout expires

Returns:

  • (BasicObject, nil) —

    the previous value or the fallback result



47
48
49
50
# File 'lib/farce/weak_atom.rb', line 47

def swap(new_value, mode: nil, timeout: nil, &)
  check_frozen!
  super(@manager.wrap(new_value, mode:), timeout:, &)
end

#update(mode: nil, timeout: nil) {|current| ... } ⇒ BasicObject?

Atomically replace the current value with the result of a block.

Parameters:

  • timeout (Numeric, nil) (defaults to: nil) —

    the maximum number of seconds to wait

  • mode (Symbol, nil) (defaults to: nil) —

    the result's mode, or nil for the default

Yields:

  • receives the current value and computes its replacement

Yield Parameters:

  • current (BasicObject, nil) —

    the current value

Yield Returns:

  • (BasicObject, nil) —

    the replacement value

Returns:

  • (BasicObject, nil) —

    the replacement value, or nil when the timeout expires



66
67
68
69
70
71
72
73
74
# File 'lib/farce/weak_atom.rb', line 66

def update(mode: nil, timeout: nil)
  raise LocalJumpError, "no block given" unless block_given?
  @manager.validate_mode!(mode)
  super(timeout:) do |current|
    value = yield(current)
    check_frozen!
    @manager.wrap(value, mode:)
  end
end

#upsert(initial, mode: nil, timeout: nil) {|current| ... } ⇒ BasicObject?

Store an initial value if the current value is nil, otherwise replace it with the result of a block.

Parameters:

  • initial_value (BasicObject, nil) —

    the value to store when the current value is nil

  • timeout (Numeric, nil) (defaults to: nil) —

    the maximum number of seconds to wait

  • mode (Symbol, nil) (defaults to: nil) —

    the replacement's mode, or nil for the default

Yields:

  • receives a non-nil current value and computes its replacement

Yield Parameters:

  • current (BasicObject) —

    the current value

Yield Returns:

  • (BasicObject, nil) —

    the replacement value

Returns:

  • (BasicObject, nil) —

    the replacement or initial value, or nil when the timeout expires



78
79
80
81
82
83
84
85
86
# File 'lib/farce/weak_atom.rb', line 78

def upsert(initial, mode: nil, timeout: nil)
  raise LocalJumpError, "no block given" unless block_given?
  @manager.validate_mode!(mode)
  @atom.update(timeout:) do |current|
    value = nil.equal?(current) ? initial : yield(current)
    check_frozen!
    @manager.wrap(value, mode:)
  end
end

#wait_until_changed(expected, timeout: nil) { ... } ⇒ BasicObject?

Wait until the current value no longer matches an expected value.

Parameters:

  • expected (BasicObject, nil) —

    the value to compare with the current value

  • timeout (Numeric, nil) (defaults to: nil) —

    the maximum number of seconds to wait

Yields:

  • called when the timeout expires

Returns:

  • (BasicObject, nil) —

    the changed value or the fallback result



104
105
106
107
108
109
110
111
112
113
# File 'lib/farce/weak_atom.rb', line 104

def wait_until_changed(expected, timeout: nil, &fallback)
  deadline = Internal.timeout_deadline(timeout)
  while true
    current = @atom.get(timeout: Internal.remaining_timeout(deadline)) { TIMED_OUT }
    return fallback&.call if TIMED_OUT.equal?(current)
    return current unless values_equal?(current, expected)
    result = @atom.wait_until_changed(current, timeout: Internal.remaining_timeout(deadline)) { TIMED_OUT }
    return fallback&.call if TIMED_OUT.equal?(result)
  end
end