piotrmurach / finite_machine Goto Github PK
View Code? Open in Web Editor NEWA minimal finite state machine with a straightforward syntax.
Home Page: https://piotrmurach.github.io/finite_machine/
License: MIT License
A minimal finite state machine with a straightforward syntax.
Home Page: https://piotrmurach.github.io/finite_machine/
License: MIT License
I don't expect the following code to transition at all. It appears the conditional transition (false) is taken anyway. Why?
require 'finite_machine'
def false?
false
end
bug = FiniteMachine.define do
initial :initial
events {
event :bump, :initial => :low, if: :false?
event :bump, :low => :medium
}
callbacks {
on_enter_event { |event| puts "#{event.name}\t#{event.from}\t=>\t#{event.to}" }
}
end
bug.bump
bug.bump
I'm building a Job object that I want to persist via ActiveRecord. I'm using Rails 4.1.1 and Ruby 1.9.3.
So following the README I've created this:
class Job < ActiveRecord::Base
validates :state, presence: true
def initialize(attrs = {})
super
@manage.restore!(state) if state
end
def manage
context = self
@manage ||= FiniteMachine.define do
target context
initial :unapproved
events {
event :enqueue, :unapproved => :pending
event :authorize, :pending => :access
}
callbacks {
on_enter_state do |event|
target.state = event.to
target.save
end
}
end
end
end
When I do this:
j = Job.new(state: :unapproved)
I get:
NoMethodError: undefined method `restore!' for nil:NilClass
I can fix that by changing the initialize method to:
def initialize(attrs = {})
super
manage.restore!(state) if state
end
But then I get this error
NoMethodError: undefined method `on_enter_state' for #<FiniteMachine::Observer:0x0000000459cb18>
I can fix this by changing the callbacks declaration to
callbacks {
on_transition do |event|
target.state = event.to
target.save
end
}
This means that every time there is a transition, the new state gets saved. However, it doesn't set the initial state. So: Job.create fails because the state is still nil, and so the validation fail.
I'm tempted to just do:
before_validation :set_initial_state, on: :create
def set_initial_state
self.state = manage.initial_state unless state
end
Should callbacks call on_enter_state, and if not how should I set the initial state?
link nie działa
na moneta nie ma zgłaszania blędów
Hey there!
I'm getting an exception when attempting to make a transition that is not defined in my state machine. I'm expecting it to not transition silently as stated in the docs under the "Dynamic choice conditions" section:
However if default state is not present and non of the conditions match, no transition is performed.
Here's the relevant code:
FiniteMachine.define do
initial :inactive
target ticket
alias_target :ticket
events {
event :advance, :from => [:inactive, :paused, :fulfilled] do
choice :active, :if => -> {
ticket.working? && !ticket.pending? && !ticket.on_hold?
}
end
event :advance, :from => [:inactive, :active, :fulfilled] do
choice :paused, :if => -> { ticket.pending? || ticket.on_hold? }
end
event :advance, :from => [:inactive, :active, :paused] do
choice :fulfilled, :if => -> { ticket.finished? }
end
}
end
For example, when in the paused
state and calling advance
while the ticket is still in the paused
state, I'm getting the following exception:
FiniteMachine::TransitionError: NoMethodError: undefined method `to_states' for nil:NilClass
occured at /home/vagrant/bundle/ruby/2.1.0/gems/finite_machine-0.10.0/lib/finite_machine/transition.rb:202:in `update_state'
/home/vagrant/bundle/ruby/2.1.0/gems/finite_machine-0.10.0/lib/finite_machine/transition.rb:232:in `block in call'
/opt/.rbenv/versions/2.1.6/lib/ruby/2.1.0/sync.rb:233:in `block in sync_synchronize'
/opt/.rbenv/versions/2.1.6/lib/ruby/2.1.0/sync.rb:230:in `handle_interrupt'
/opt/.rbenv/versions/2.1.6/lib/ruby/2.1.0/sync.rb:230:in `sync_synchronize'
/home/vagrant/bundle/ruby/2.1.0/gems/finite_machine-0.10.0/lib/finite_machine/two_phase_lock.rb:26:in `synchronize'
/home/vagrant/bundle/ruby/2.1.0/gems/finite_machine-0.10.0/lib/finite_machine/threadable.rb:14:in `sync_exclusive'
/home/vagrant/bundle/ruby/2.1.0/gems/finite_machine-0.10.0/lib/finite_machine/transition.rb:229:in `call'
/home/vagrant/bundle/ruby/2.1.0/gems/finite_machine-0.10.0/lib/finite_machine/state_machine.rb:283:in `block in transition'
/opt/.rbenv/versions/2.1.6/lib/ruby/2.1.0/sync.rb:233:in `block in sync_synchronize'
/opt/.rbenv/versions/2.1.6/lib/ruby/2.1.0/sync.rb:230:in `handle_interrupt'
/opt/.rbenv/versions/2.1.6/lib/ruby/2.1.0/sync.rb:230:in `sync_synchronize'
/home/vagrant/bundle/ruby/2.1.0/gems/finite_machine-0.10.0/lib/finite_machine/two_phase_lock.rb:26:in `synchronize'
/home/vagrant/bundle/ruby/2.1.0/gems/finite_machine-0.10.0/lib/finite_machine/threadable.rb:14:in `sync_exclusive'
/home/vagrant/bundle/ruby/2.1.0/gems/finite_machine-0.10.0/lib/finite_machine/state_machine.rb:274:in `transition'
/home/vagrant/bundle/ruby/2.1.0/gems/finite_machine-0.10.0/lib/finite_machine/event.rb:96:in `block in call'
/opt/.rbenv/versions/2.1.6/lib/ruby/2.1.0/sync.rb:233:in `block in sync_synchronize'
/opt/.rbenv/versions/2.1.6/lib/ruby/2.1.0/sync.rb:230:in `handle_interrupt'
/opt/.rbenv/versions/2.1.6/lib/ruby/2.1.0/sync.rb:230:in `sync_synchronize'
/home/vagrant/bundle/ruby/2.1.0/gems/finite_machine-0.10.0/lib/finite_machine/two_phase_lock.rb:26:in `synchronize'
/home/vagrant/bundle/ruby/2.1.0/gems/finite_machine-0.10.0/lib/finite_machine/threadable.rb:14:in `sync_exclusive'
/home/vagrant/bundle/ruby/2.1.0/gems/finite_machine-0.10.0/lib/finite_machine/event.rb:91:in `call'
/home/vagrant/bundle/ruby/2.1.0/gems/finite_machine-0.10.0/lib/finite_machine/event_builder.rb:61:in `block in define_event_transition'
(irb):3:in `irb_binding'
I'm able to avoid the exception by adding a few more conditional transitions:
event :advance, :from => :active do
choice :active, :silent => true
end
event :advance, :from => :paused do
choice :paused, :silent => true
end
event :advance, :from => :fulfilled do
choice :fulfilled, :silent => true
end
However, it'd be nice if I didn't have to add this boilerplate.
Am I doing something wrong here? Please let me know if you'd like me to provide any more context.
Thanks!
@craiglittle Cancelling inside callbacks is buggy, i.e. returning integer matching CANCELLED
halts the callbacks stack. Which leads me to think, that either the CANCELLED
should be a unique value or when callbacks evaluate to false
the stack is halted. Any thoughts?
@craiglittle There is quite few methods that do not convert easily to dynamic method calls, such as :fail
, it seems safer and more programatic to allow just trigger
as the API gateway for events firing?
Is the initial pseudo state implicit? I think it would be wise to have a way to initiate the transition into the first state with an explicit call to initial!
.
require 'finite_machine'
phone = FiniteMachine.define do
initial :first
callbacks {
on_before { |event| puts "event #{event.name}" }
on_enter(:first) { |event| puts "transition #{event.name} #{event.from} -> #{event.to}" }
}
end
phone.start!
This would produce output:
event start!
transition INITIAL -> first
This initial transition could also be done as the last step in define
but by having an explicit start!
call the user has some control over when the initial transition is triggered.
Does this make sense?
Apparently :transition is not a valid event name. Rather than crashing the ruby interpreter it would be nice to get an exception.
require 'finite_machine'
fsm = FiniteMachine.define do
event :transition, :a => :b
end
fsm.transition
$ ruby examples/finite_machine/callbacks.rb
/Users/kjw/.rvm/gems/ruby-1.9.3-p484@fsmtalk/gems/finite_machine-0.6.1/lib/finite_machine/transition.rb:130: [BUG] Segmentation fault
ruby 1.9.3p484 (2013-11-22 revision 43786) [x86_64-darwin12.5.0]
-- Control frame information -----------------------------------------------
c:2657 p:0018 s:9297 b:7969 l:001468 d:007968 LAMBDA /Users/kjw/.rvm/gems/ruby-1.9.3-p484@fsmtalk/gems/finite_machine-0.6.1/lib/finite_machine/transition.rb:130
c:2656 p:---- s:7965 b:7965 l:007964 d:007964 FINISH
It looks like this code results in infinite recursion.
I am using Sequel models as a target for FiniteMachine. The trouble is that when I initialize the finite_machine object ruby recognizes the to_hash method (I believe introduced by the json_serializer plugin) and puts the model's values into **options instead of putting the model itself into *args
This would be an issue for anything that Ruby recognizes as a hash, not just Sequel models.
my_model = MyModel.create(a: 1, b: 2)
my_machine = FiniteMachine.new(my_model) do
# etc.
end
my_machine.target == my_model # false
I'm setting my_machine.env.target = my_model
immediately after creating the machine.
Allow FiniteMachine.new(target: my_model) ?
my_machine.set_target(target) method ?
Warn if there are keys in **options that aren't recognized ?
Great gem! Really nicely designed with the level of customization I was looking for. 😄
Now to my one little problem...
I have a lot of custom methods which I use in if
conditionals, etc. which do not belong in the target
and which in any case ideally I'd like to access directly in transition blocks. In the readme the recommendation is to write code like this:
on_enter_start do |event|
target.turn_engine_on
end
on_exit_start do |event|
target.turn_engine_off
end
But as complexity is added to the machine, I would really prefer to just call turn_engine_on
etc. here and define that method on the machine itself. Especially if (as in my case) turn_engine_on
is not a method that only relates to the state machine logic and does not belong on the target.
I can do this by subclassing FiniteMachine::StateMachine
, which works more or less, but it means that to define the machine itself I need to override initialize
and define the block in there, which doesn't seem right. The recommended way is to use FiniteMachine::Definition
, but doing that means that you can't define these local methods on the finite machine itself, since internally FiniteMachine::Definition.new
hard-codes FiniteMachine
here:
finite_machine/lib/finite_machine/definition.rb
Lines 39 to 44 in b956cf3
What would be the recommended way to define methods for the machine which are locally accessible in block contexts? I feel this is an important issue when thinking about encapsulating machine-specific logic within the machine itself rather than in the target.
Thanks for reading 😄 And keep up the great work on the gem.
According to the docs:
you can create separate events under the same name for each transition that needs combining.
Example finite machine:
class TestMachine < FiniteMachine::Definition
initial :state_1
events do
event :event_1, from: :state_3, to: :state_4
event :event_1, from: :state_1 do
choice :state_2, if: -> { false }
choice :state_3
end
end
end
a = TestMachine.new
a.state #=> :state_1
a.event_1 #=> expected transition to :state_3
a.state #=> :state_4
Interestingly enough, switching the order of the defined events fixes the problem:
class TestMachine < FiniteMachine::Definition
initial :state_1
events do
event :event_1, from: :state_1 do
choice :state_2, if: -> { false }
choice :state_3
end
event :event_1, from: :state_3, to: :state_4
end
end
a = TestMachine.new
a.state #=> :state_1
a.event_1
a.state #=> :state_3
Lots of warnings triggered when used with ruby 3.1.2
With ruby 3.1.2
bundle exec rspec
Error:
lib/finite_machine/observer.rb:22:in
block in cleanup_callback_queue': undefined local variable or method
callback_queue' for FiniteMachine::Observer:Class (NameError)
Specs pass without warnings
master
Description of the problem
Hi, I m using the inheritance approach for my problem. Both the base class and subclass share the same event. On triggering the event, I expected the transition in my subclass to get fired. But only the base class transition happens.
How would the new feature work?
Ideally, it would be effective to see the transitions and callbacks in the subclass overrides the base class incase of same event.
Example:
class GenericStateMachine < FiniteMachine::Definition
initial :red
event :start, :red => :green
on_after_start { target.first_callback() }
end
class SpecificStateMachine < GenericStateMachine
event :start, :red => :yellow
on_after_start { target.second_callback() }
end
On triggering the event start, the state turns to yellow and second_callback method is invoked
I'm trying to extend the FiniteMachine::Definition class and am unable to define my own constructor (#initialize
). The #new
method is directly overridden in definition.rb
and its superclass in state_machine.rb
, which prevents developers from creating their own constructors on classes extending either FiniteMachine::Definition or FiniteMachine.
class StateManager < FiniteMachine::Definition
attr_accessor :state_attr
def initialize(*args)
@state_attr = args[0]
end
end
# trying to initialize the class above by calling `StateManager.new(:some_attr)` will invoke
# FiniteMachine::Definition's implementation of `#new`, short-circuiting any `initialize` method
# created on the custom class
Custom classes are unable to define their own #initialize
method (constructor) on classes extending classes from the FiniteMachine gem.
Custom classes should be able to define their own #initialize
method (constructor) on classes extending classes from the FiniteMachine gem.
I'm upgrading a Rails app from FiniteState 0.11.3 to 0.13.0 (the upgrade seems to be needed to work with Ruby 2.7.1).
Currently, I have the following code:
#app/models/concerns/with_state.rb
MyStateMachine.state_machine.new(self)
#app/state_machines/my_state_machine.rb
class MyStateMachine < FiniteMachine::Definition
alias_target :model
callbacks do
on_transition do |event|
model.state = event.to
end
end
end
Which, due the change in the API, has been updated as follows (based on https://github.com/piotrmurach/finite_machine#291-alias_target)
#app/models/concerns/with_state.rb
MyStateMachine.state_machine.new(self, alias_target: :model)
#app/state_machines/my_state_machine.rb
class MyStateMachine < FiniteMachine::Definition
on_transition do |event|
model.state = event.to
end
end
But the change raises the following error
NameError:
undefined local variable or method `model' for #FiniteMachine::StateMachine:0x00007f8e12c073b0
This is not really a big issue for me since it can be fixed with target.state = event.to
but perhaps it can cause some problems for other users.
Is the error something expected?
I needed a general callbacks to be fired before specific ones, so I created a monkey patch to specify high priority any event/state:
HIGH_PRIORITY_ANY_EVENT = :high_priority_any_event
HIGH_PRIORITY_ANY_STATE = :high_priority_any_state
def trigger(event, *args, &block)
sync_exclusive do
[event.type].each do |event_type|
[HIGH_PRIORITY_ANY_STATE, HIGH_PRIORITY_ANY_EVENT, event.name, ANY_STATE, ANY_EVENT].each do |event_name|
hooks.call(event_type, event_name) do |hook|
handle_callback(hook, event)
off(event_type, event_name, &hook) if hook.is_a?(Once)
end
end
end
end
end
and I am using it like that:
on_enter :high_priority_any_state do |event|
end
It would be nice if I could specify a priority for callbacks, for example the default priority could be 0, and if I would like to "general" callback to be fired before "specific" one I could do something like:
on_enter priority: 1 do |event|
end
or to be more explicit
on_enter :any, priority: 1 do |event|
end
Hello
I like your coding style and the separation of the state machine and the target class.
I'd like the state machine to set the initial value according to a value from the target class.
Something like
require 'finite_machine'
class Video
attr_accessor :state
def initialize
self.state = 'pending'
end
def state_machine
@state_machine ||= FiniteMachine.define do
initial self.state
target self
events do
event :enqueue_generate, :pending => :waiting_for_generation
event :generate, :waiting_for_generation => :generating
event :finish_generation, :generating => :waiting_for_upload
event :enqueue_youtube_upload, :waiting_for_upload => :uploading
event :finish_upload, :uploading => :uploaded
end
callbacks do
on_enter do |obj, event|
puts "on_enter from #{event.from} to #{event.to}"
end
on_exit do |obj, event|
puts "on_exit from #{event.from} to #{event.to}"
end
on_transition do |obj, event|
puts "on_transition from #{event.from} to #{event.to}"
end
end
end
end
end
v = Video.new
v.state
# "pending"
v.state_machine.generate
# FiniteMachine::TransitionError: inappropriate current state 'none'
How can I sync the state of the machine with the state of a variable in my target class ?
Basically I'd like to save the state of the state machine in an ActiveRecord class.
Thanks !
Geoffroy
Does this gem support ruby 2.6.0
version?
Found that model_object.finite_machine.current
with ruby "ruby-2.6.0-preview3" returns an array of Proc instead if current state.
Different behaviors:
2.5.0 :005 > Order.first.finite_machine
Order Load (0.9ms) SELECT "orders".* FROM "orders" ORDER BY "orders"."number" DESC LIMIT $1 [["LIMIT", 1]]
=> <#FiniteMachine::StateMachine:0x3ff971d6b294 @states=[:none, :cart, :pending, :prepared, :received, :delivered, :sent, :any, :canceled], @events=[:init, :create, :prepare, :receive, :dispatch, :deliver, :cancel], @transitions=[{:none=>:cart}, {:cart=>:pending}, {:pending=>:prepared}, {:prepared=>:received}, {:delivered=>:received}, {:prepared=>:sent}, {:sent=>:delivered}, {:any=>:canceled}]>
2.5.0 :006 > Order.first.finite_machine.current
Order Load (0.7ms) SELECT "orders".* FROM "orders" ORDER BY "orders"."number" DESC LIMIT $1 [["LIMIT", 1]]
=> :cart
2.6.0-preview3 :009 > Order.first.finite_machine
Order Load (0.8ms) SELECT "orders".* FROM "orders" ORDER BY "orders"."number" DESC LIMIT $1 [["LIMIT", 1]]
=> #<Class:0x00007fd1f921aef8>
2.6.0-preview3 :010 > Order.first.finite_machine.current
Order Load (0.7ms) SELECT "orders".* FROM "orders" ORDER BY "orders"."number" DESC LIMIT $1 [["LIMIT", 1]]
=> [#<Proc:0x00007fd1f92416c0>, #<Proc:0x00007fd1f9240748>, #<Proc:0x00007fd1f924bc38>, #<Proc:0x00007fd1f924ad60>, #<Proc:0x00007fd1f924a428>, #<Proc:0x00007fd1f9249000>, #<Proc:0x00007fd1f9248768>]
Code example (model and concern with finite machine):
module OrderFiniteMachine
extend ActiveSupport::Concern
included do
before_validation :set_initial_state, on: :create
after_initialize :restore_state
after_find :restore_state
end
def finite_machine
context = self
@state_engine ||= FiniteMachine.define do
target context
initial :cart
events do
event :create, cart: :pending
event :prepare, pending: :prepared
event :receive, prepared: :received
event :dispatch, prepared: :sent
event :deliver, sent: :delivered
event :receive, delivered: :received
event :cancel, any: :canceled
end
end
end
private
def set_initial_state
self.state = finite_machine.current
end
end
# == Schema Information
#
# Table name: orders
#
# id :integer not null, primary key
# state :string
# created_at :datetime not null
# updated_at :datetime not null
#
class Order < ApplicationRecord
include OrderFiniteMachine
end
I'd love to see a version of can?
and cannot?
that takes into account the conditional transitions (if:
and :unless
). It seems this gem is set up for this perfectly since the logic of if a state can transition has been separated from the actual transition code.
In my opinion the on_enter
and on_exit
callbacks for events are confusing.
States and events are different concepts. By allowing on_enter
and on_exit
on events a single namespace is created for states and events leading to ambiguous code. I.e. when you have an event :digit
and a state :digit
what does the callback on_enter_digit
mean?
I propose to have separate before
and after
callbacks for events:
callbacks {
before_digit { |event, digit| digits << digit }
after_on_hook { |event| disconnect! }
}
What was your rationale for merging callbacks for states and events?
Given the following program to show the order of callbacks:
require 'finite_machine'
fsm = FiniteMachine.define do
initial :previous
events {
event :go, :previous => :next, if: -> { puts "checking condition"; true }
}
callbacks {
on_exit_state { |event| puts "exit_#{event.from}" }
on_enter_event { |event| puts "\tbefore_#{event.name}" }
on_enter(:any) { |event| puts "\t\ttransition: #{event.name}: #{event.from} -> #{event.to}" }
on_enter_state { |event| puts "\tenter_#{event.to}" }
on_exit_event { |event| puts "after_#{event.name}" }
}
end
fsm.go
The output is:
checking condition
exit_previous
before_go
transition: go: previous -> next
enter_next
after_go
I would prefer and expect the following order:
before_go
checking condition
exit_previous
transition: go: previous -> next
enter_next
after_go
For reference see http://en.wikipedia.org/wiki/UML_state_machine#Transition_execution_sequence.
NoMethodError: undefined method <=' for #<NoMethodError:0x0000003c773d50> from /home/jason/.rvm/gems/ruby-2.1.2/gems/finite_machine-0.7.1/lib/finite_machine/catchable.rb:58:in
block in handler_for_error'
Just started getting these, and haven't had any time to figure out why.
@craiglittle I feel as though we should drop the on_transition
callback, I haven't used it myself and don't really see a reason any more for its existence?
The currently released version of finite_machine - v0.12.1 - raises LoadError (cannot load such file -- sync)
on requiring the library.
This was fixed in (an unreleased) v0.13.0 here.
It seems that the problem slipped through the CI, since coveralls
also depends on sync
transitively through tins
. But that gem is part of a "metrics" group in the Gemfile
and not installed at runtime.
Ensure that coveralls & sync gem are not installed.
git clone [email protected]:piotrmurach/finite_machine.git -b v0.12.1
cd finite_machine
bundle install --without=metrics
ruby -r./lib/finite_machine -e 'puts "works"'
LoadError
is raised due to sync
dependency missing.
No LoadError
is raised.
Hi, I do believe there is a misbehaving of the Finite Machine. When I am transitioning to a new state and if an error occurs and raises an exception in the callback I am expecting that the transition does not happen. However what is happening is that the Finite Machine current state after failure is the new state instead of the initial one.
Code sample:
require 'finite_machine'
fsm = FiniteMachine.define do
initial :green
events { event :slow, :green => :yellow }
callbacks { on_enter { raise RuntimeError } }
end
begin
puts fsm.current
# green
fsm.slow
rescue RuntimeError
puts fsm.current
# yellow (I would expect the "green" state here)
end
Note: Using the on_before
callback it works but that is because it does not change the current state to the new one.
Transition does not happen if any_state is used in the definition
class MySM < FiniteMachine::Definition
# Does not work - definition accepted but blocks transition
event :shouldturnon, from: any_state, to: :on
# These work fine
event :turnson, to: :on
event :init, none: :off
# Log state transitions
on_before do |event|
if event.from != event.to
puts 'Working state change from '\
"#{event.from} to #{event.to}"
else
puts 'Impossible state change from '\
"#{event.from} to #{event.to}"
end
end
on_enter(:off) do |event|
puts 'Off fired OK'
end
on_enter(:on) do
puts 'This is never triggered if any_state is used in definition'
end
end
mymachine = MySM.new
#Works
mymachine.init
#Does not work
mymachine.shouldturnon
#Same thing works
mymachine.turnson
Transition does not happen if any_state is used in the from definition
Transition should happen
This worked fine with earlier ruby/finite_machine where I used the
event :turnoff, any: :off
notation. Bit me when I upgraded.
Hi,
I have a little problem because I would like read terminated state but I can't If:
terminal [:failed, :finished]
state_machine.on_terminated do |event|
...
end
Thanks
Peter
a random problem, message queue spec sometimes hangs on JRuby, but not always
https://github.com/piotrmurach/finite_machine/blob/master/spec/unit/message_queue_spec.rb
travis
it just hangs the build, no exception
it shouldn't hang
travis / jruby-9.2.1.1 and jruby-head
a random exception after the build
https://travis-ci.org/github/piotrmurach/finite_machine/jobs/684379413
test suite
Coverage report generated for spec to /home/travis/build/piotrmurach/finite_machine/coverage. 884 / 895 LOC (98.77%) covered.
[Coveralls] Submitting to https://coveralls.io/api/v1
Coveralls encountered an exception:
Java::JavaUtilConcurrent::RejectedExecutionException
Task java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask@4057babc[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@db9ecd[Wrapped task = org.jruby.ext.timeout.Timeout$TimeoutTask@23ac164d]] rejected from java.util.concurrent.ScheduledThreadPoolExecutor@274de4bc[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2055)
java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:825)
java.base/java.util.concurrent.ScheduledThreadPoolExecutor.delayedExecute(ScheduledThreadPoolExecutor.java:340)
java.base/java.util.concurrent.ScheduledThreadPoolExecutor.schedule(ScheduledThreadPoolExecutor.java:562)
org.jruby.dist/org.jruby.ext.timeout.Timeout.yieldWithTimeout(Timeout.java:143)
org.jruby.dist/org.jruby.ext.timeout.Timeout.timeout(Timeout.java:116)
org.jruby.dist/org.jruby.ext.timeout.Timeout.timeout(Timeout.java:92)
org.jruby.dist/org.jruby.ext.timeout.Timeout$INVOKER$s$timeout.call(Timeout$INVOKER$s$timeout.gen)
...
should calculate the coverage
travis
@kjwierenga As requested in this issue I came around to think that this should be implemented. My current thoughts on semantics would be to allow specifying branch states as an array, like so:
event :next, :company_form => [:agreement_form,:promo_form],
if: -> (registrant, user) { user.has_promo? }
Depending on the event condition we would choose appropriate state. One important question to ask is how to distinguish between event conditional logic and state condition. Currently the :if/:unless
allow you to guard the transition, that is, ensure the transition succeeds or fails. However, the branching condition would allow to choose between possible states, e.i. it would always succeed. One suggestion would be to use another key argument
event :next, :company_form => [:agreement_form,:promo_form],
branch: -> (registrant, user) { user.has_promo? }
Any thoughts?
Ok this seems like it has to be a bug.
In my model:
def get_state
log "Setting current state to {self.current_state}"
self.current_state
end
def fm
context = self
@fm ||= FiniteMachine.define to
target context
initial (context.get_state || :pending)
#...more follows
Among others, I have the following event:
event :finalized [:ready, :valid] => :final
And the following callbacks --
on_enter :ready do |event|
log "current state: #{state}"
log "In :ready callback doing #{event.from} to #{event.to}"
end
on_enter :final |event|
log "In :final callback"
end
When I call
item.fm.finalized
for an item that is already in the :ready state, here's what the log looks like:
Setting current state to ready
current state: ready
In :ready callback doing none to ready
In :final callback
I don't understand why the item starting at the none state (and you can see from the log that it's in the :ready state.) I assume it's doing the none -> ready callback as part of the initialization, but is that right? I don't believe any callbacks should be done until after the initial state is set.
The repo description url at the top of the page is broken.
It should be: http://piotrmurach.github.io/finite_machine/
😄
@kjwierenga Another area I was thinking about is the ability to allow for substates or sub machines. Of course, according to UML you could have one or the other for a given state. I think this would allow to build far more complex state machines. On the other hand, I believe that this would require a lot of work implementation wise since even the definition of current state gets really blurry. Any thoughts?
Pardon me for making this an issue. It's really a question.
I'm having trouble persisting the state. More specifically, I'd like to instantiate the state machine with the prior saved state.
All I've been able to find is the DSL that lets me define an initial state, or define an event that transitions to an initial state. Both require me to define the initial state at coding time.
fm = FiniteMachine.define do
initial :red
fm = FiniteMachine.define do
events {
event :start, :none => :green
In practice, I'm defining a "standalone" along the lines of,
class Engine < FiniteMachine::Definition
initial :neutral
What I'd like is to define the initial state in the initializer for that class, something like:
class Engine < FiniteMachine::Definition
def initialize(car, state)
initial state
target car
end
However that does not work. Should it? I get :none
as the current state after initialization. Still reading the code.
Why inherited state machines doesn't inherit callbacks and what can be done to fix it?
For example SpecificStateMachine that inherits from GenericStateMachine doesn't inherit "on_enter" callback:
class GenericStateMachine < FiniteMachine::Definition
callbacks do
on_enter do |event|
target.state = event.to
end
end
end
class SpecificStateMachine < GenericStateMachine
callbacks do
on_after :accept do |event|
#TODO
end
end
end
I'm playing with finite_machine and I can’t seem to get this very simple example to work. It pushes through a couple of states on a single (repeated) event. Why doesn’t this work?
require 'finite_machine'
bug = FiniteMachine.define do
initial :initial
events {
event :bump, :initial => :low
event :bump, :low => :medium
event :bump, :medium => :high
}
callbacks {
on_enter_event { |event| puts "#{event.name}\t#{event.from}\t=>\t#{event.to}" }
}
end
bug.bump
bug.bump
bug.bump
Output is:
$ ruby examples/bug.rb
bump medium => high
bump initial => high
bump low => high
What am I doing wrong? Or is this a bug?
@kjwierenga I'm planning to add event deferring reference
My suggestion for syntax would be to do
wait_until_transition :state_name { |event| .... }
This would save the callback in internal queue and wait until the particular condition is met. In similar manner I would have wait_unitl_enter
, wait_until_exit
etc... Does it sound reasonable?
I expect this code to output 3 digits (911). But it doesn't. Is the self transition :dialing => :dialing
causing problems?
require 'finite_machine'
phone = FiniteMachine.define do
initial :on_hook
digits = []
events {
# On Hook
event :digit, :on_hook => :dialing
# Dialing
event :digit, :dialing => :dialing
event :off_hook, :dialing => :alerting
}
callbacks {
on_enter(:digit) { |event, digit| digits << digit; puts "digit: #{digit}" }
on_enter(:off_hook) { |event| puts "Dialling #{digits.join}" }
on_transition { |event|
puts "%s [ %s -> %s ]" %
[event.name, event.from.capitalize, event.to.capitalize]
}
}
end
phone.digit(9)
phone.digit(1)
phone.digit(1)
phone.off_hook
on_transition is not setting @to correctly
Code:
require 'finite_machine'
require 'pp'
class StopLight < FiniteMachine::Definition
events {
initial :red
event :power_on, :off => :red
event :power_off, :any => :off
event :change, :red => :green
event :change, :green => :yellow
event :change, :yellow => :red
}
callbacks {
on_transition do |event|
pp event
end
}
end
sl = StopLight.new
loop do
puts "***Change"
sl.change
if rand(5) == 0
puts "*** Whoops. Lost power!"
sl.power_off
end
sleep 1
puts
end
This exception at the the following output is excepted and correct. The unexpected behavior is @to is nil in TransitionEvent
jblack@comet:~$ ruby light.rb
***Change
#<FiniteMachine::TransitionEvent:0x00000001fa0658
@from=:red,
@name=:change,
@to=:green>
***Change
#<FiniteMachine::TransitionEvent:0x00000001eff690
@from=:green,
@name=:change,
@to=:yellow>
***Change
#<FiniteMachine::TransitionEvent:0x00000001ea98a8
@from=:yellow,
@name=:change,
@to=:red>
***Change
#<FiniteMachine::TransitionEvent:0x00000001f6a800
@from=:red,
@name=:change,
@to=:green>
*** Whoops. Lost power!
#<FiniteMachine::TransitionEvent:0x00000001ec98b0
@from=:green,
@name=:power_off,
@to=nil>
***Change
/var/lib/gems/1.9.1/gems/finite_machine-0.9.0/lib/finite_machine/state_machine.rb:251:in `valid_state?': inappropriate current state 'off' (FiniteMachine::InvalidStateError)
There might be a reason to define events like "success" or "fail". For "fail" event that error appear:
FiniteMachine::AlreadyDefinedError: You tried to define an event named "fail", however this would generate "instance" method "fail", which is already defined by FiniteMachine
I spent quite some time until I figured that FM instances are not independent, they share a common queue/event source, which actually causes a lockup when two independent threads (foo, bar) use two - seemingly independent - SM instances (A & B) where thread foo from the callback of SM A wants to
In this case the SM will lock up in a deadlock.
Here's the code snippet attached to reproduce the issue - you can set WORKAROUND_ACTIVE to activate a workaround with a trade-off. You can signal TTIN to the app while being locked up to see the issue.
This may be a design limitation but then it should be noted in the - otherwise excellent - documentation. I found this out the 'hard way' by getting an unexpected and seemingly inexplicable thread lockup.
What is the best way? Could you provide some examples?
The transition event object isn't populated with the proper to
state.
Example Machine:
class TestMachine < FiniteMachine::Definition
initial :pending
events {
event :confirm, from: :pending do
choice :confirmed, if: -> { false }
choice :manual_review
end
event :confirm, from: :manual_review do
choice :confirmed, if: -> { true }
choice :manual_review
end
}
callbacks {
on_enter do |event|
puts event.inspect
end
}
end
Usage:
t = TestMachine.new
t.state #=> :pending
t.confirm
#<FiniteMachine::TransitionEvent:0x007fdeb9a20a68 @name=:confirm, @from=:pending, @to=:manual_review>
t.state #=> :manual_review
t.confirm
#<FiniteMachine::TransitionEvent:0x007fdeb9a5e8e0 @name=:confirm, @from=:manual_review, @to=nil> ** INCORRECT - @to should be :confirmed **
t.state #=> :confirmed
In the example above, @to
should be :confirmed
in the second TransitionEvent
I understand the basics of creating state machines, although what I'm noticing is a state machine for one state machine. In some of the stuff I do, I might have three different states to check for. Is there currently a way to implement this in the normal way, or should I just approach it like I have been:
Create three classes in a module. Within each class have three self referenced methods. In each method, create a state machine that detect the state for different "method states", and point to each defined method state. For simple stuff this is fine, but for more complex things, this can get kind of complicated.
This is less an issue with a specific algorithm, and more something I'd like to do: have handling for different states for different parts of the system. For example, per analogy I used:
Fold paper air plane.
Check if paper air plane soars.
If paper air planes is able to fly, keep model type in data model.
Else if paper air plane crashes, restart algorithm with different folded model.
Repeat until working plane model is found.
Granted this is an evolutionary model in a different sense than is found in Machine Learning. But each model would theoretically have its own state machine.
It appears there's a memory leak in the v0.11
series of the gem. When we bumped to it in our app, we started to experience severe memory issues in production.
I don't have time at the moment to do a deeper dive, but I wanted to make you aware of the issue in case you might have an inkling off the top of your head about where the problem might be. For the time being, we've had to revert back to v0.10.x
until we can get to the bottom of it.
Some event names collide with instance methods in FiniteMachine.
events {
event :subscribe, [:zero, :time, :count] => :subscription_refundable
}
You tried to define an event named "subscribe", however this would generate "instance" method "subscribe", which is already defined by FiniteMachine
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.