How do I know if a guard rejected a transistion - spring-statemachine

I have transitions configured as in the reference documentation:
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal()
.source(States.SI).target(States.S1)
.event(Events.E1)
.guard(guard1())
.and()
.withExternal()
.source(States.S1).target(States.S2)
.event(Events.E1)
.guard(guard2())
.and()
.withExternal()
.source(States.S2).target(States.S3)
.event(Events.E2)
.guardExpression("extendedState.variables.get('myvar')");
}
If the state machine is in state States.S1 and I send event Events.E1, how do I know if guard2() rejected the transition?
I'm currently checking the state of the state machine: if it's still in States.S1 then I know the event was rejected. Is this the "right" way to handle rejection by a guard?
Edit:
After reading Janne's comment I realized what I'm trying to do is probably an incorrect use of a guard. It seems that guards should be used only to determine which state the machine should transition to and not whether or not a state should be entered. If it was the latter, i.e. the transition was outright rejected, then the state machine would be in no state. I should have been tipped off by what the code allows me to do and by having a state machine-centric mindset when coding the workflow. That's what I get for rushing before a holiday!

Related

Spring statemachine how to persist a machine with nested regions

I have statemachine with configuration mentioned at the end, which i want to persist in the database. I am following this tutorial https://docs.spring.io/spring-statemachine/docs/3.1.0/reference/#statemachine-examples-datajpamultipersist to persist it.
However, when my statemachine is in PARALLEL_TASKS state then i see only one row in database
is it not suppose to show 3 rows (1 for parent state PARALLEL_TASKS and 2 for sub-states UNLOCKING_EXCESSIVE_POINTS_STARTED, PROCESSING_PAYMENT_STARTED)?
Can someone please tell me how can i fix it? what is wrong with my configuration?
#Configuration
#EnableStateMachineFactory(name = "SampleConfig")
#Qualifier("SampleConfig")
public class SampleConfig extends EnumStateMachineConfigurerAdapter<OrderState, OrderEvent> {
#Autowired
private JpaPersistingStateMachineInterceptor<OrderState, OrderEvent, String> persister;
#Override
public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
states
.withStates()
.initial(OrderState.ORDER_CREATED)
.state(OrderState.ORDER_CREATED)
.state(OrderState.PARALLEL_TASKS)
.end(OrderState.ORDER_COMPLETED)
.and()
.withStates()
.parent(OrderState.PARALLEL_TASKS)
.region("R1")
.initial(OrderState.UNLOCKING_EXCESSIVE_POINTS_STARTED)
.state(OrderState.UNLOCKING_EXCESSIVE_POINTS_STARTED)
.state(OrderState.UNLOCKED_EXCESSIVE_POINTS)
.and()
.withStates()
.parent(OrderState.PARALLEL_TASKS)
.region("R2")
.initial(OrderState.PROCESSING_PAYMENT_STARTED)
.state(OrderState.PROCESSING_PAYMENT_STARTED)
.state(OrderState.PROCESSED_PAYMENT)
;
}
#Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
transitions
.withExternal()
.source(OrderState.ORDER_CREATED)
.target(OrderState.PARALLEL_TASKS)
.event(OrderEvent.ORDER_SUBMITTED_EVENT)
.and()
.withExternal()
.source(OrderState.UNLOCKING_EXCESSIVE_POINTS_STARTED)
.target(OrderState.UNLOCKED_EXCESSIVE_POINTS)
.event(OrderEvent.UNLOCKED_POINTS_SUCCESS)
.and()
.withExternal()
.source(OrderState.PROCESSING_PAYMENT_STARTED)
.target(OrderState.PROCESSED_PAYMENT)
.event(OrderEvent.PAYMENT_PROCESSED_SUCCESS);
}
#Override
public void configure(StateMachineConfigurationConfigurer<OrderState, OrderEvent> config) throws Exception {
config.withConfiguration()
.autoStartup(false)
.regionExecutionPolicy(RegionExecutionPolicy.PARALLEL)
.and()
.withPersistence()
.runtimePersister(persister)
;
}
}
Ok, I really don't like to say this because I am big fan of Spring State Machine but to tell you the truth persistence is not one of its big fortes, it is most of the time giving me the feeling, it is done to be able to say 'look we have persistence capablities'.
State Machines can be quite complex (look to the UML Specification) and I don't believe persistance layer is designed to deal with all of these complexities.
If you are using Spring State Machine for modelling Shopping Charts, Booking, etc let me ask what would be your (Spring State Machine's) strategy when your business case evolves, what happens when you have to restore a State Machine persisted with the previous release and in this release your business evolved and you have complete different State Machine design.
I found the following framework does much better with above mentioned problems, Akka Finite State Machine with it's Event / Schema Evolution capabilities. It has also a separate Persistance layer that you have much better control so you will not suffer scenarios as you mentioned above.
Now what you describe above seems like a bug in Spring State Machine and you might get a fix for it, but I strongly advice you to check Akka Framework before you invest more your project resource to Spring State Machine, Spring State Machine is awesome for some problems but not for everything.
Now when you read the Akka Documentation, you will see that it is written in Scala and it is more natural to program your State Machine with Scala (it is possible to program with Java but it gets too clumsy ), if you are reluctant to program in Scala, it is possible to program it as Scala / Java hybrid as I demonstrated in these blogs blog1, blog2.
Event / Schema Evolution capabilities is also demonstrated in the following blog3.
I know this is not answer for what you are asking but as a developer probably walk the same road as you, I like to give a fair warning.

Allow-listing IP addresses using `call.cancel()` from within `EventListener.dnsEnd()` in OkHttp

i am overriding the dnsEnd() function in EventListener:
#Override
public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
inetAddressList.forEach(address -> {
logger.debug("checking if url ({}) is in allowlist", address.toString());
if (!allowlist.contains(address)) {
call.cancel();
}
});
}
i know, in the documentation it says not to alter call parameters etc:
"All event methods must execute fast, without external locking, cannot throw exceptions, attempt to mutate the event parameters, or be re-entrant back into the client. Any IO - writing to files or network should be done asynchronously."
but, as i don't care about the call if it is trying to get to an address outside the allowlist, i fail to see the issue with this implementation.
I want to know if anyone has experience with this, and why it may be an issue?
I tested this and it seems to work fine.
This is fine and safe. Probably the strangest consequence of this is the canceled event will be triggered by the thread already processing the DNS event.
But cancelling is not the best way to constrain permitted IP addresses to a list. You can instead implement the Dns interface. Your implementation should delegate to Dns.SYSTEM and them filter its results to your allowlist. That way you don't have to worry about races on cancelation.

Ambiguous Timer Behaviour

I've a workflow where one of the state is supposed to do a status poll, so basically whenever I land in that state I want to run an action and then do the same every 'n' seconds until I get the expected response.
Following is the state machine config, START & INTERMEDIATE states are running in polling fashion ( retying till I get intended response )
StateMachineBuilder.Builder<StateMachineConfig.States, StateMachineConfig.Events> builder = StateMachineBuilder.builder();
builder.configureConfiguration()
.withConfiguration()
.machineId("Test-" + i)
.taskScheduler(scheduler);
builder.configureStates()
.withStates()
.initial(StateMachineConfig.States.START)
.state(StateMachineConfig.States.START)
.state(StateMachineConfig.States.INTERMEDIATE)
.state(StateMachineConfig.States.STOP)
.end(StateMachineConfig.States.STOP);
builder.configureTransitions()
.withExternal()
.source(StateMachineConfig.States.START)
.target(StateMachineConfig.States.START)
.action(startAction)
.timer(30000)
.and()
.withExternal()
.source(StateMachineConfig.States.START)
.target(StateMachineConfig.States.INTERMEDIATE)
.event(StateMachineConfig.Events.GO1)
.and()
.withExternal()
.source(StateMachineConfig.States.INTERMEDIATE)
.target(StateMachineConfig.States.INTERMEDIATE)
.timer(30000)
.action(intermediateAction)
.and()
.withExternal()
.source(StateMachineConfig.States.INTERMEDIATE)
.target(StateMachineConfig.States.STOP)
.event(StateMachineConfig.Events.GO2)
.and()
.withInternal()
.source(StateMachineConfig.States.STOP)
.action(stopAction);
StateMachine stateMachine = builder.build();
stateMachine.start();
The issue here is, as soon as state machine starts startAction runs, it sends the event GO1 which takes the state-machine to INTERMEDIATE, but the intermediateAction does not run immediately it waits for full 30 seconds before that, while the startAction does not do that.
I think the culprit being, both TimerTriggers are scheduled in doStart() in the initial phases of state-machine life cycle and both these triggers are processed as soon as we enter the START state. And when I get to the INTERMEDIATE state I do not have an '0th time' timer trigger event to process. Is this the intended behavior? If yes, how do I achieve this kind of functionality where I want to run an action every 'n' seconds including '0th time'
Repo to reproduce the issue: https://github.com/shethchintan7/spring-state-machine-thread-leak

MassTransit fault consumer not invoked for request/response

What is the best practice for handling exceptions in MassTransit 3+ with regard to Request/Response pattern? The docs here mention that if a ResponseAddress exists on a message, the Fault message will be sent to that address, but how does one consumer/receive the messages at that address? The ResponseAddress for Bus.Request seems to be an auto-generated MassTransit address that I don't have control over, so I don't know how to access the exception thrown in the main consumer. What am I missing? Here's my code to register the consumer and its fault consumer using Unity container:
cfg.ReceiveEndpoint(host, "request_response_queue", e =>
{
e.Consumer<IConsumer<IRequestResponse>>(container);
e.Consumer(() => container.Resolve<IMessageFaultConsumer<IRequestResponse>>() as IConsumer<Fault<IRequestResponse>>);
});
And here's my attempt at a global message fault consumer:
public interface IMessageFaultConsumer<TMessage>
{
}
public class MessageFaultConsumer<TMessage> : IConsumer<Fault<TMessage>>, IMessageFaultConsumer<TMessage>
{
public Task Consume(ConsumeContext<Fault<TMessage>> context)
{
Console.WriteLine("MessageFaultConsumer");
return Task.FromResult(0);
}
}
This approach DOES work when I use Bus.Publish as opposed to Bus.Request. I also looked into creating an IConsumeObserver and putting my global exception logging code into the ConsumeFault method, but that has the downside of being invoked every exception prior to the re-tries giving up. What is the proper way to handle exceptions for request/response?
First of all, the request/response support in MassTransit is meant to be used with the .Request() method, or the request client (MessageRequestClient or PublishRequestClient). With these methods, if the consumer of the request message throws an exception, that exception is packaged into the Fault<T>, which is sent to the ResponseAddress. Since the .Request() method, and the request client are both asynchronous, using await will throw an exception with the exception data from the fault included. That's how it is designed, await the request and it will either complete, timeout, or fault (throw an exception upon await).
If you are trying to put in some global "exception handler" code for logging purposes, you really should log those at the service boundary, and an observer is the best way to handle it. This way, you can just implement the ConsumeFault method, and log to your event sink. However, this is synchronous within the consumer pipeline, so recognize the delay that could be introduced.
The other option is to of course just consume Fault<T>, but as you mentioned, it does not get published when the request client is used with the response address in the header. In this case, perhaps your requester should publish an event indicating that operation X faulted, and you can log that -- at the business context level versus the service level.
There are many options here, it's just choosing the one that fits your use case best.

Is it possible to add additional information for crashes handled by Xamarin.Insights analytics framework

I have an xamarin.android with xamarin.insights intergrated.
Right now every time I handle error manually (try/catch) I'm adding information about environment (staging/production):
try
{
ExceptionThrowingFunction();
}
catch (Exception exception)
{
exception.Data["Environment"] = "staging";
throw;
}
But this information is missing in case if error handled by xamarin.insights itself (in case of crash).
It is possible to add additional exception data in case of crash?
docs reference I used
From reading the docs page reference that you mentioned, I still get the impression that you have to call the .Report method as well as in:-
Insights.Report(exception, new Dictionary <string, string> {
{"Some additional info", "foobar"}
});
What I believe they are saying in this example:-
try {
ExceptionThrowingFunction();
}
catch (Exception exception) {
exception.Data["AccountType"] = "standard";
throw;
}
Is that you have the ability when any Exception is encountered, to package additional information that you can later send to the Insights server, as the Data property of the Exception is just a Key/Value Dictionary.
So if you had an Exception several layers deep, you can choose to re-throw the Exception with additional information contained within it that you will later send to the Insights server.
At a higher level, you can then take the Exception that was thrown deeper down the call-hierarchy and then call the Insights.Report, with:-
Insights.Report(
{the rethrown exception in your higher up try..catch block},
{rethrown exception}.Data
);
that will then send all the additional Key/Value information previously captured.
From seeing your last part of your question though it looks like you are interested in Insights handling and sending this additional .Data automatically should there be an unhandled exception.
If it is not currently being sent, then perhaps suggest to them that this can be sent also? As it sounds a feasible request for this to automatically be sent as well incase of an unhandled exception.
Update 1:-
Yes - I understand about the unhandled exception scenario now that you are referring to.
I have not dealt with this component directly, so there may be hooks / event handlers or something already defined where you can tap into this, and execute some custom code just prior to this being sent.
If this is not available, then perhaps suggest this to them to include as its a Beta product?
Alternatively, you could still achieve this yourself by capturing the unhandled exceptions just prior to them falling. You'd have to code this however on each platform.
For instance on Windows Phone in the App class there is Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e) to which you could then supplement the Exception thrown with this extra .Data.
For Android you could take a look at this post that describes how to catch uncaughtException that will help you in capturing the unhandled exceptions.
Whether just supplementing the Exception in these handlers above is enough all depends on how they've written their hook into this, as to how well it behaves and whether it is executed first, prior to their implementation.
You will have to try and see if it does. If it doesn't behave well, allowing you to supplement extra data prior to the automatic call to Insights, you have another fallback solution, to just do the .Report call manually within these unhandled exception handlers yourself to make this work and supplement the extra .Data to achieve your aim.

Resources