Class: Farce::Envelope
- Inherits:
-
Object
- Object
- Farce::Envelope
- Includes:
- Abstract::Value, Internal::Noncopyable, Shareable, Internal::Inspect
- Defined in:
- lib/farce/envelope.rb,
lib/farce/integrations/active_support/blank.rb
Overview
An envelope can be used to wrap an unshareable value in a shareable object.
Once created, the envelope can be shared between Ractors without any objects being moved or copied until the envelope is opened.
When a Ractor attempts to open an envelope, it attempts "claim" it. Some envelopes may only be claimed by a single Ractor, others can be claimed by any Ractor. Claims cannot be taken back.
Defined Under Namespace
Classes: Copy, Local, Move, Share
Constant Summary collapse
- AlreadyClaimed =
Raised when trying to claim an envelope that has already been claimed by another Ractor.
Class.new(Ractor::IsolationError)
ActiveSupport Integration collapse
-
#blank? ⇒ Boolean
Checks if the envelope's value is blank without claiming it.
Methods included from Internal::Noncopyable
Class Method Summary collapse
-
.new(value) ⇒ Envelope
Creates an envelope wrapping the given value.
Instance Method Summary collapse
-
#claim ⇒ Envelope?
Attempts to claim the envelope for the current Ractor.
-
#claim! ⇒ Envelope
Attempts to claim the envelope for the current Ractor.
-
#claimed? ⇒ Boolean
trueif the envelope has been claimed by any Ractor,falseotherwise. -
#initialize(value) ⇒ Envelope
constructor
A new instance of Envelope.
-
#owned? ⇒ Boolean
trueif the envelope has been claimed by the current Ractor,falseotherwise. -
#same_value?(other, identity: false) ⇒ Boolean
Compares wrapped values.
-
#value ⇒ BasicObject
Attempts to claim the envelope and retrieves the value if successful.
Methods included from Abstract::Value
Methods included from Shareable
Constructor Details
#initialize(value) ⇒ Envelope
Returns a new instance of Envelope.
282 283 284 285 286 |
# File 'lib/farce/envelope.rb', line 282 def initialize(_, auto_unwrap = nil) raise ArgumentError, "auto_unwrap needs to be shareable" unless Ractor.shareable?(auto_unwrap) @auto_unwrap = auto_unwrap super() end |
Class Method Details
.new(value, move: false) ⇒ Envelope .new(value, local: false) ⇒ Envelope .new(value, mode:) ⇒ Envelope
The keyword arguments are only accepted by Envelope itself, not by its subclasses.
Also, if multiple keyword arguments are provided, mode takes precedence over local, which takes precedence
over move.
Creates an envelope wrapping the given value. If the value is Ractor-shareable, a Share envelope will be created. Otherwise, the type of envelope will be determined by the keyword arguments.
260 261 262 263 264 |
# File 'lib/farce/envelope.rb', line 260 def self.new(value, *, **) return super if self != Envelope return Share.new(value, *) if Ractor.shareable?(value) subclass_for(**).new(value, *) end |
Instance Method Details
#blank? ⇒ Boolean
This methods is only available if ActiveSupport has been loaded.
Checks if the envelope's value is blank without claiming it.
57 58 59 60 61 62 63 64 65 66 |
# File 'lib/farce/integrations/active_support/blank.rb', line 57 def blank? if !claimed? && (vault = comparison_vault) result = vault.is_blank?(comparison_key) # A concurrent claim may have removed the value from the vault. return result unless claimed? end return true unless owned? value.blank? end |
#claim ⇒ Envelope?
Attempts to claim the envelope for the current Ractor.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
# File 'lib/farce/envelope.rb', line 24 class Envelope include Internal::Noncopyable include Internal::Inspect VAULT_ATOM = Internal::Atom.new private_constant :VAULT_ATOM include Shareable include Abstract::Value # Raised when trying to claim an envelope that has already been claimed by another Ractor. AlreadyClaimed = Class.new(Ractor::IsolationError) module Copyable include Internal::Copyable define_method(:dup, Object.instance_method(:dup)) define_method(:clone, Object.instance_method(:clone)) private def initialize_copy(other) Object.instance_method(:initialize_copy).bind_call(self, other) end end private_constant :Copyable # An envelope that copies its contents. Can be opened by multiple Ractors. # The value will be copied once when the envelope is created, and then once per Ractor that opens the envelope. # # @!method new(value) # @!scope class # @param (see Farce::Envelope#initialize) # @return [Copy, Share] A new envelope wrapping the given value. class Copy < Farce::Envelope include Copyable # @overload initialize(value) # @param [Object] value The value to wrap in the envelope. def initialize(value, auto_unwrap = nil) @vault = VAULT_ATOM.store_if_absent { Internal::Vault.new } super @vault.copy_in(self, value) end # @api private def marshal_dump storage = Internal::Storage.ractor [1, storage.key?(self) ? storage[self] : retrieve, auto_unwrap] end # @api private def marshal_load(data) value, manager = Internal::MarshalSupport.payload(data, 2) initialize(value, manager) Internal::Storage.ractor[self] = value end # (see Envelope#claim) def claim = self # (see Envelope#claimed?) def claimed? = true # (see Envelope#owned?) def owned? = true protected def retrieve = @vault.copy_out(vault_key) private def initialize_dup(other) value = other.retrieve @vault_key = Object.new.freeze super @vault.copy_in(@vault_key, value) end def initialize_clone(other, freeze: nil) value = other.retrieve @vault_key = Object.new.freeze super @vault.copy_in(@vault_key, value) end def vault_key = defined?(@vault_key) ? @vault_key : self end # An envelope that moves its contents. Can only be opened by a single Ractor. # The value will be moved once when the envelope is created, and then once when the envelope is opened. # # @!method new(value) # @!scope class # @param (see Farce::Envelope#initialize) # @return [Move, Share] A new envelope wrapping the given value. class Move < Farce::Envelope include Internal::MarshalSupport::Reject include Shareable::Unfreezable # @overload initialize(value) # @param [Object] value The value to wrap in the envelope. def initialize(value, auto_unwrap = nil) @vault = VAULT_ATOM.store_if_absent { Internal::Vault.new } @owner = Internal::Atom.new super @vault.move_in(self, value) end # (see Envelope#claim) def claim owner = @owner.store_if_absent { Ractor.current } self if owner == Ractor.current end # (see Envelope#claimed?) def claimed? = !@owner.value.nil? # (see Envelope#owned?) def owned? = @owner.value == Ractor.current private def retrieve = @vault.move_out(self) end # An envelope that keeps its contents local to the Ractor that created it. # Can only be opened by the Ractor that created it. # # @!method new(value) # @!scope class # @param (see Farce::Envelope#initialize) # @return [Local, Share] A new envelope wrapping the given value. class Local < Farce::Envelope include Copyable # @overload initialize(value) # @param value [Object] The value to wrap in the envelope. def initialize(value, auto_unwrap = nil) @owner = Ractor.current Internal::Storage.ractor[self] = value super end # @api private def marshal_dump raise TypeError, "local envelope belongs to another Ractor" unless owned? [1, Internal::Storage.ractor[self], auto_unwrap] end # @api private def marshal_load(data) value, manager = Internal::MarshalSupport.payload(data, 2) initialize(value, manager) end # (see Envelope#claim) def claim = owned? ? self : nil # (see Envelope#claimed?) def claimed? = true # (see Envelope#owned?) def owned? = @owner == Ractor.current private def initialize_copy(other) super other.claim! Internal::Storage.ractor[self] = other.value.dup end end # An envelope that wraps a Ractor-shareable value. Can be opened by any Ractor. # Defeats the purpose of an envelope, but is provided for code that expects an envelope. # # @!method new(value) # @!scope class # @param (see Farce::Envelope#initialize) # @return [Share] A new envelope wrapping the given value. class Share < Farce::Envelope include Copyable attr_reader :value # @overload initialize(value) # @param value [Object] The value to wrap in the envelope. Must be shareable. def initialize(value, auto_unwrap = nil) raise ArgumentError, "value must be shareable" unless Ractor.shareable?(value) @value = value super end # @api private def marshal_dump = [1, value, auto_unwrap] # @api private def marshal_load(data) value, manager = Internal::MarshalSupport.payload(data, 2) initialize(Ractor.make_shareable(value), manager) end # (see Envelope#claim) def claim = self # (see Envelope#claimed?) def claimed? = true # (see Envelope#owned?) def owned? = true end # @note # The keyword arguments are only accepted by Envelope itself, not by its subclasses. # Also, if multiple keyword arguments are provided, `mode` takes precedence over `local`, which takes precedence # over `move`. # # Creates an envelope wrapping the given value. If the value is Ractor-shareable, a {Share} envelope will be # created. Otherwise, the type of envelope will be determined by the keyword arguments. # # @overload new(value, move: false) # @param value [BasicObject] The value to wrap in the envelope. # @param move [Boolean] Whether to create a {Move} envelope. Ignored if `local` or `mode` is provided. # @return [Envelope] A new envelope wrapping the given value. # # @overload new(value, local: false) # @param value [BasicObject] The value to wrap in the envelope. # @param local [Boolean] Whether to create a {Local} envelope. Ignored if `mode` is provided. # @return [Envelope] A new envelope wrapping the given value. # # @overload new(value, mode:) # @param value [BasicObject] The value to wrap in the envelope. # @param mode [Symbol, nil] The type of envelope to create. Can be `:move`, `:copy`, or `:local`. # @return [Envelope] A new envelope wrapping the given value. # # @return [Envelope] A new envelope wrapping the given value. def self.new(value, *, **) return super if self != Envelope return Share.new(value, *) if Ractor.shareable?(value) subclass_for(**).new(value, *) end def self.subclass_for(move: false, local: false, mode: nil) case mode when :move then Move when :copy then Copy when :local then Local when nil then local ? Local : (move ? Move : Copy) else raise ArgumentError, "invalid mode: #{mode.inspect}" end end private_class_method :subclass_for # @api private attr_reader :auto_unwrap # @overload initialize(value) # @param value [BasicObject] The value to wrap in the envelope. def initialize(_, auto_unwrap = nil) raise ArgumentError, "auto_unwrap needs to be shareable" unless Ractor.shareable?(auto_unwrap) @auto_unwrap = auto_unwrap super() end # Attempts to claim the envelope for the current Ractor. # Raises an exception if it was unable to claim the envelope. # @return [Envelope] The envelope if the claim was successful. # @raise [AlreadyClaimed] If the envelope has already been claimed by another Ractor. def claim! = claim || raise(AlreadyClaimed, "envelope has already been claimed by another Ractor") # Attempts to claim the envelope and retrieves the value if successful. # May be called multiple times. # @return [BasicObject] The value wrapped in the envelope. # @raise [AlreadyClaimed] If the envelope has already been claimed by another Ractor. def value = Internal::Storage.ractor.store_if_absent(self) { claim! && retrieve } # Compares wrapped values. Compatible envelopes can be compared without # claiming or opening either envelope. # @param other [BasicObject, Envelope] The value or envelope to compare. # @param identity [Boolean] Whether to compare the values by identity instead of equality. # @return [Boolean] Whether the wrapped values match. def same_value?(other, identity: false) if comparison_vault other_vault = other.comparison_vault if Envelope === other if comparison_vault.equal?(other_vault) return comparison_vault.same_value?(comparison_key, other.comparison_key, identity:) elsif !(Envelope === other) && Ractor.shareable?(other) return comparison_vault.same_value?(comparison_key, other, identity:, right_stored: false) end end left = value right = Envelope === other ? other.value : other return BasicObject.instance_method(:equal?).bind_call(left, right) if identity left == right end # @api private def inspect_with(inspector) super do inspector.breakable inspector.from_envelope(self, "value=") end end protected def comparison_vault = defined?(@vault) && @vault def comparison_key = defined?(@vault_key) ? @vault_key : self end |
#claim! ⇒ Envelope
Attempts to claim the envelope for the current Ractor. Raises an exception if it was unable to claim the envelope.
292 |
# File 'lib/farce/envelope.rb', line 292 def claim! = claim || raise(AlreadyClaimed, "envelope has already been claimed by another Ractor") |
#claimed? ⇒ Boolean
Returns true if the envelope has been claimed by any Ractor, false otherwise.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
# File 'lib/farce/envelope.rb', line 24 class Envelope include Internal::Noncopyable include Internal::Inspect VAULT_ATOM = Internal::Atom.new private_constant :VAULT_ATOM include Shareable include Abstract::Value # Raised when trying to claim an envelope that has already been claimed by another Ractor. AlreadyClaimed = Class.new(Ractor::IsolationError) module Copyable include Internal::Copyable define_method(:dup, Object.instance_method(:dup)) define_method(:clone, Object.instance_method(:clone)) private def initialize_copy(other) Object.instance_method(:initialize_copy).bind_call(self, other) end end private_constant :Copyable # An envelope that copies its contents. Can be opened by multiple Ractors. # The value will be copied once when the envelope is created, and then once per Ractor that opens the envelope. # # @!method new(value) # @!scope class # @param (see Farce::Envelope#initialize) # @return [Copy, Share] A new envelope wrapping the given value. class Copy < Farce::Envelope include Copyable # @overload initialize(value) # @param [Object] value The value to wrap in the envelope. def initialize(value, auto_unwrap = nil) @vault = VAULT_ATOM.store_if_absent { Internal::Vault.new } super @vault.copy_in(self, value) end # @api private def marshal_dump storage = Internal::Storage.ractor [1, storage.key?(self) ? storage[self] : retrieve, auto_unwrap] end # @api private def marshal_load(data) value, manager = Internal::MarshalSupport.payload(data, 2) initialize(value, manager) Internal::Storage.ractor[self] = value end # (see Envelope#claim) def claim = self # (see Envelope#claimed?) def claimed? = true # (see Envelope#owned?) def owned? = true protected def retrieve = @vault.copy_out(vault_key) private def initialize_dup(other) value = other.retrieve @vault_key = Object.new.freeze super @vault.copy_in(@vault_key, value) end def initialize_clone(other, freeze: nil) value = other.retrieve @vault_key = Object.new.freeze super @vault.copy_in(@vault_key, value) end def vault_key = defined?(@vault_key) ? @vault_key : self end # An envelope that moves its contents. Can only be opened by a single Ractor. # The value will be moved once when the envelope is created, and then once when the envelope is opened. # # @!method new(value) # @!scope class # @param (see Farce::Envelope#initialize) # @return [Move, Share] A new envelope wrapping the given value. class Move < Farce::Envelope include Internal::MarshalSupport::Reject include Shareable::Unfreezable # @overload initialize(value) # @param [Object] value The value to wrap in the envelope. def initialize(value, auto_unwrap = nil) @vault = VAULT_ATOM.store_if_absent { Internal::Vault.new } @owner = Internal::Atom.new super @vault.move_in(self, value) end # (see Envelope#claim) def claim owner = @owner.store_if_absent { Ractor.current } self if owner == Ractor.current end # (see Envelope#claimed?) def claimed? = !@owner.value.nil? # (see Envelope#owned?) def owned? = @owner.value == Ractor.current private def retrieve = @vault.move_out(self) end # An envelope that keeps its contents local to the Ractor that created it. # Can only be opened by the Ractor that created it. # # @!method new(value) # @!scope class # @param (see Farce::Envelope#initialize) # @return [Local, Share] A new envelope wrapping the given value. class Local < Farce::Envelope include Copyable # @overload initialize(value) # @param value [Object] The value to wrap in the envelope. def initialize(value, auto_unwrap = nil) @owner = Ractor.current Internal::Storage.ractor[self] = value super end # @api private def marshal_dump raise TypeError, "local envelope belongs to another Ractor" unless owned? [1, Internal::Storage.ractor[self], auto_unwrap] end # @api private def marshal_load(data) value, manager = Internal::MarshalSupport.payload(data, 2) initialize(value, manager) end # (see Envelope#claim) def claim = owned? ? self : nil # (see Envelope#claimed?) def claimed? = true # (see Envelope#owned?) def owned? = @owner == Ractor.current private def initialize_copy(other) super other.claim! Internal::Storage.ractor[self] = other.value.dup end end # An envelope that wraps a Ractor-shareable value. Can be opened by any Ractor. # Defeats the purpose of an envelope, but is provided for code that expects an envelope. # # @!method new(value) # @!scope class # @param (see Farce::Envelope#initialize) # @return [Share] A new envelope wrapping the given value. class Share < Farce::Envelope include Copyable attr_reader :value # @overload initialize(value) # @param value [Object] The value to wrap in the envelope. Must be shareable. def initialize(value, auto_unwrap = nil) raise ArgumentError, "value must be shareable" unless Ractor.shareable?(value) @value = value super end # @api private def marshal_dump = [1, value, auto_unwrap] # @api private def marshal_load(data) value, manager = Internal::MarshalSupport.payload(data, 2) initialize(Ractor.make_shareable(value), manager) end # (see Envelope#claim) def claim = self # (see Envelope#claimed?) def claimed? = true # (see Envelope#owned?) def owned? = true end # @note # The keyword arguments are only accepted by Envelope itself, not by its subclasses. # Also, if multiple keyword arguments are provided, `mode` takes precedence over `local`, which takes precedence # over `move`. # # Creates an envelope wrapping the given value. If the value is Ractor-shareable, a {Share} envelope will be # created. Otherwise, the type of envelope will be determined by the keyword arguments. # # @overload new(value, move: false) # @param value [BasicObject] The value to wrap in the envelope. # @param move [Boolean] Whether to create a {Move} envelope. Ignored if `local` or `mode` is provided. # @return [Envelope] A new envelope wrapping the given value. # # @overload new(value, local: false) # @param value [BasicObject] The value to wrap in the envelope. # @param local [Boolean] Whether to create a {Local} envelope. Ignored if `mode` is provided. # @return [Envelope] A new envelope wrapping the given value. # # @overload new(value, mode:) # @param value [BasicObject] The value to wrap in the envelope. # @param mode [Symbol, nil] The type of envelope to create. Can be `:move`, `:copy`, or `:local`. # @return [Envelope] A new envelope wrapping the given value. # # @return [Envelope] A new envelope wrapping the given value. def self.new(value, *, **) return super if self != Envelope return Share.new(value, *) if Ractor.shareable?(value) subclass_for(**).new(value, *) end def self.subclass_for(move: false, local: false, mode: nil) case mode when :move then Move when :copy then Copy when :local then Local when nil then local ? Local : (move ? Move : Copy) else raise ArgumentError, "invalid mode: #{mode.inspect}" end end private_class_method :subclass_for # @api private attr_reader :auto_unwrap # @overload initialize(value) # @param value [BasicObject] The value to wrap in the envelope. def initialize(_, auto_unwrap = nil) raise ArgumentError, "auto_unwrap needs to be shareable" unless Ractor.shareable?(auto_unwrap) @auto_unwrap = auto_unwrap super() end # Attempts to claim the envelope for the current Ractor. # Raises an exception if it was unable to claim the envelope. # @return [Envelope] The envelope if the claim was successful. # @raise [AlreadyClaimed] If the envelope has already been claimed by another Ractor. def claim! = claim || raise(AlreadyClaimed, "envelope has already been claimed by another Ractor") # Attempts to claim the envelope and retrieves the value if successful. # May be called multiple times. # @return [BasicObject] The value wrapped in the envelope. # @raise [AlreadyClaimed] If the envelope has already been claimed by another Ractor. def value = Internal::Storage.ractor.store_if_absent(self) { claim! && retrieve } # Compares wrapped values. Compatible envelopes can be compared without # claiming or opening either envelope. # @param other [BasicObject, Envelope] The value or envelope to compare. # @param identity [Boolean] Whether to compare the values by identity instead of equality. # @return [Boolean] Whether the wrapped values match. def same_value?(other, identity: false) if comparison_vault other_vault = other.comparison_vault if Envelope === other if comparison_vault.equal?(other_vault) return comparison_vault.same_value?(comparison_key, other.comparison_key, identity:) elsif !(Envelope === other) && Ractor.shareable?(other) return comparison_vault.same_value?(comparison_key, other, identity:, right_stored: false) end end left = value right = Envelope === other ? other.value : other return BasicObject.instance_method(:equal?).bind_call(left, right) if identity left == right end # @api private def inspect_with(inspector) super do inspector.breakable inspector.from_envelope(self, "value=") end end protected def comparison_vault = defined?(@vault) && @vault def comparison_key = defined?(@vault_key) ? @vault_key : self end |
#owned? ⇒ Boolean
Returns true if the envelope has been claimed by the current Ractor, false otherwise.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
# File 'lib/farce/envelope.rb', line 24 class Envelope include Internal::Noncopyable include Internal::Inspect VAULT_ATOM = Internal::Atom.new private_constant :VAULT_ATOM include Shareable include Abstract::Value # Raised when trying to claim an envelope that has already been claimed by another Ractor. AlreadyClaimed = Class.new(Ractor::IsolationError) module Copyable include Internal::Copyable define_method(:dup, Object.instance_method(:dup)) define_method(:clone, Object.instance_method(:clone)) private def initialize_copy(other) Object.instance_method(:initialize_copy).bind_call(self, other) end end private_constant :Copyable # An envelope that copies its contents. Can be opened by multiple Ractors. # The value will be copied once when the envelope is created, and then once per Ractor that opens the envelope. # # @!method new(value) # @!scope class # @param (see Farce::Envelope#initialize) # @return [Copy, Share] A new envelope wrapping the given value. class Copy < Farce::Envelope include Copyable # @overload initialize(value) # @param [Object] value The value to wrap in the envelope. def initialize(value, auto_unwrap = nil) @vault = VAULT_ATOM.store_if_absent { Internal::Vault.new } super @vault.copy_in(self, value) end # @api private def marshal_dump storage = Internal::Storage.ractor [1, storage.key?(self) ? storage[self] : retrieve, auto_unwrap] end # @api private def marshal_load(data) value, manager = Internal::MarshalSupport.payload(data, 2) initialize(value, manager) Internal::Storage.ractor[self] = value end # (see Envelope#claim) def claim = self # (see Envelope#claimed?) def claimed? = true # (see Envelope#owned?) def owned? = true protected def retrieve = @vault.copy_out(vault_key) private def initialize_dup(other) value = other.retrieve @vault_key = Object.new.freeze super @vault.copy_in(@vault_key, value) end def initialize_clone(other, freeze: nil) value = other.retrieve @vault_key = Object.new.freeze super @vault.copy_in(@vault_key, value) end def vault_key = defined?(@vault_key) ? @vault_key : self end # An envelope that moves its contents. Can only be opened by a single Ractor. # The value will be moved once when the envelope is created, and then once when the envelope is opened. # # @!method new(value) # @!scope class # @param (see Farce::Envelope#initialize) # @return [Move, Share] A new envelope wrapping the given value. class Move < Farce::Envelope include Internal::MarshalSupport::Reject include Shareable::Unfreezable # @overload initialize(value) # @param [Object] value The value to wrap in the envelope. def initialize(value, auto_unwrap = nil) @vault = VAULT_ATOM.store_if_absent { Internal::Vault.new } @owner = Internal::Atom.new super @vault.move_in(self, value) end # (see Envelope#claim) def claim owner = @owner.store_if_absent { Ractor.current } self if owner == Ractor.current end # (see Envelope#claimed?) def claimed? = !@owner.value.nil? # (see Envelope#owned?) def owned? = @owner.value == Ractor.current private def retrieve = @vault.move_out(self) end # An envelope that keeps its contents local to the Ractor that created it. # Can only be opened by the Ractor that created it. # # @!method new(value) # @!scope class # @param (see Farce::Envelope#initialize) # @return [Local, Share] A new envelope wrapping the given value. class Local < Farce::Envelope include Copyable # @overload initialize(value) # @param value [Object] The value to wrap in the envelope. def initialize(value, auto_unwrap = nil) @owner = Ractor.current Internal::Storage.ractor[self] = value super end # @api private def marshal_dump raise TypeError, "local envelope belongs to another Ractor" unless owned? [1, Internal::Storage.ractor[self], auto_unwrap] end # @api private def marshal_load(data) value, manager = Internal::MarshalSupport.payload(data, 2) initialize(value, manager) end # (see Envelope#claim) def claim = owned? ? self : nil # (see Envelope#claimed?) def claimed? = true # (see Envelope#owned?) def owned? = @owner == Ractor.current private def initialize_copy(other) super other.claim! Internal::Storage.ractor[self] = other.value.dup end end # An envelope that wraps a Ractor-shareable value. Can be opened by any Ractor. # Defeats the purpose of an envelope, but is provided for code that expects an envelope. # # @!method new(value) # @!scope class # @param (see Farce::Envelope#initialize) # @return [Share] A new envelope wrapping the given value. class Share < Farce::Envelope include Copyable attr_reader :value # @overload initialize(value) # @param value [Object] The value to wrap in the envelope. Must be shareable. def initialize(value, auto_unwrap = nil) raise ArgumentError, "value must be shareable" unless Ractor.shareable?(value) @value = value super end # @api private def marshal_dump = [1, value, auto_unwrap] # @api private def marshal_load(data) value, manager = Internal::MarshalSupport.payload(data, 2) initialize(Ractor.make_shareable(value), manager) end # (see Envelope#claim) def claim = self # (see Envelope#claimed?) def claimed? = true # (see Envelope#owned?) def owned? = true end # @note # The keyword arguments are only accepted by Envelope itself, not by its subclasses. # Also, if multiple keyword arguments are provided, `mode` takes precedence over `local`, which takes precedence # over `move`. # # Creates an envelope wrapping the given value. If the value is Ractor-shareable, a {Share} envelope will be # created. Otherwise, the type of envelope will be determined by the keyword arguments. # # @overload new(value, move: false) # @param value [BasicObject] The value to wrap in the envelope. # @param move [Boolean] Whether to create a {Move} envelope. Ignored if `local` or `mode` is provided. # @return [Envelope] A new envelope wrapping the given value. # # @overload new(value, local: false) # @param value [BasicObject] The value to wrap in the envelope. # @param local [Boolean] Whether to create a {Local} envelope. Ignored if `mode` is provided. # @return [Envelope] A new envelope wrapping the given value. # # @overload new(value, mode:) # @param value [BasicObject] The value to wrap in the envelope. # @param mode [Symbol, nil] The type of envelope to create. Can be `:move`, `:copy`, or `:local`. # @return [Envelope] A new envelope wrapping the given value. # # @return [Envelope] A new envelope wrapping the given value. def self.new(value, *, **) return super if self != Envelope return Share.new(value, *) if Ractor.shareable?(value) subclass_for(**).new(value, *) end def self.subclass_for(move: false, local: false, mode: nil) case mode when :move then Move when :copy then Copy when :local then Local when nil then local ? Local : (move ? Move : Copy) else raise ArgumentError, "invalid mode: #{mode.inspect}" end end private_class_method :subclass_for # @api private attr_reader :auto_unwrap # @overload initialize(value) # @param value [BasicObject] The value to wrap in the envelope. def initialize(_, auto_unwrap = nil) raise ArgumentError, "auto_unwrap needs to be shareable" unless Ractor.shareable?(auto_unwrap) @auto_unwrap = auto_unwrap super() end # Attempts to claim the envelope for the current Ractor. # Raises an exception if it was unable to claim the envelope. # @return [Envelope] The envelope if the claim was successful. # @raise [AlreadyClaimed] If the envelope has already been claimed by another Ractor. def claim! = claim || raise(AlreadyClaimed, "envelope has already been claimed by another Ractor") # Attempts to claim the envelope and retrieves the value if successful. # May be called multiple times. # @return [BasicObject] The value wrapped in the envelope. # @raise [AlreadyClaimed] If the envelope has already been claimed by another Ractor. def value = Internal::Storage.ractor.store_if_absent(self) { claim! && retrieve } # Compares wrapped values. Compatible envelopes can be compared without # claiming or opening either envelope. # @param other [BasicObject, Envelope] The value or envelope to compare. # @param identity [Boolean] Whether to compare the values by identity instead of equality. # @return [Boolean] Whether the wrapped values match. def same_value?(other, identity: false) if comparison_vault other_vault = other.comparison_vault if Envelope === other if comparison_vault.equal?(other_vault) return comparison_vault.same_value?(comparison_key, other.comparison_key, identity:) elsif !(Envelope === other) && Ractor.shareable?(other) return comparison_vault.same_value?(comparison_key, other, identity:, right_stored: false) end end left = value right = Envelope === other ? other.value : other return BasicObject.instance_method(:equal?).bind_call(left, right) if identity left == right end # @api private def inspect_with(inspector) super do inspector.breakable inspector.from_envelope(self, "value=") end end protected def comparison_vault = defined?(@vault) && @vault def comparison_key = defined?(@vault_key) ? @vault_key : self end |
#same_value?(other, identity: false) ⇒ Boolean
Compares wrapped values. Compatible envelopes can be compared without claiming or opening either envelope.
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 |
# File 'lib/farce/envelope.rb', line 305 def same_value?(other, identity: false) if comparison_vault other_vault = other.comparison_vault if Envelope === other if comparison_vault.equal?(other_vault) return comparison_vault.same_value?(comparison_key, other.comparison_key, identity:) elsif !(Envelope === other) && Ractor.shareable?(other) return comparison_vault.same_value?(comparison_key, other, identity:, right_stored: false) end end left = value right = Envelope === other ? other.value : other return BasicObject.instance_method(:equal?).bind_call(left, right) if identity left == right end |
#value ⇒ BasicObject
Attempts to claim the envelope and retrieves the value if successful. May be called multiple times.
298 |
# File 'lib/farce/envelope.rb', line 298 def value = Internal::Storage.ractor.store_if_absent(self) { claim! && retrieve } |