Email Reminders using two boundary event timers

I really appreciate your feedback on this. I was “translating” my problem to something simpler, and I think I messed up the above snippet in translation. I figure maybe looking at the actual code I am using would be helpful.

I followed your second example, and while it produces viable BPMN, I believe that because I am in a subprocess that something else is going awry.

Here is my actual problemspace. I send a user a link to order lunch, and remind them to do so every ten minutes

.subProcess()
.embeddedSubProcess()
.startEvent()

// This just makes sure there is a 'placedOrder' in the process
.serviceTask()
    .withClass(InstantiateOrderPlacedDelegate::class)

//Assign them an order
.userTask("placeOrders")
    .name("Place your order at: #{orderLink}")
    .userForm("placedOrder", "I placed my order!", UserFormType.BOOLEAN.type, "false")
    .assignee("#{lunchGetter.id}")


    .boundaryEvent("killUserTask")
    .timerWithDuration("PT1H")
        .cancelActivity(true)
        .moveToActivity<UserTaskBuilder>("placeOrders")

    .boundaryEvent()
        .timerWithCycle("R3/PT10M")
        .cancelActivity(false)
        .serviceTask("reminderIfNeeded")
        .withClass(OrderLunchReminderDelegate::class)
        .endEvent()
        .moveToActivity<UserTaskBuilder>("placeOrders")

.serviceTask("timesUpOrOrderComplete")
    .name("timesUpOrOrderComplete")
    .implementation("expression")
    .camundaExpression("\${1 + 1}")

.moveToNode("killUserTask").connectTo("timeUpOrOrderComplete")

.endEvent()
.subProcessDone()
    .multiInstance()
        .parallel()
        .camundaCollection("#{gettingLunch}")
        .camundaElementVariable("lunchGetter")
    .multiInstanceDone<SubProcessBuilder>()

What I am seeing is the reminders get generated many more than the three I expect, and something just run until my JVM runs out of memory.

I think process wise what you’ve shown should work, but something under the hood of Camunda is behaving strangely. Any ideas on if subprocesses and timecycles have strange interactions?

Can you post proper executable fluent api code. The exmaple you are giving is not valid

So here is executable code:

       BpmnModelInstance model = Bpmn.createExecutableProcess('model')
                .startEvent()
                .subProcess()
                    .embeddedSubProcess()
                    .startEvent()
                    .manualTask()
                    .userTask("placeOrders")
                        .name("Place your order at: 1234")
                        .camundaAssignee("someUser")
                        .boundaryEvent("killUserTask")
                            .timerWithDuration("PT1H")
                            .cancelActivity(true)
                            .moveToActivity("placeOrders")
                        .boundaryEvent()
                            .timerWithCycle("R3/PT10M")
                            .cancelActivity(false)
                            .manualTask("reminderIfNeeded")
                            .endEvent()
                            .moveToActivity("placeOrders")
                    .serviceTask("timesUpOrOrderComplete")
                        .name("timesUpOrOrderComplete")
                        .implementation("expression")
                        .camundaExpression("\${1 + 1}")
                    .moveToNode("killUserTask").connectTo("timesUpOrOrderComplete")
                    .endEvent()
                .subProcessDone()
                    .multiInstance()
                    .parallel()
                    .camundaCollection("#{gettingLunch}")
                    .camundaElementVariable("lunchGetter")
                    .multiInstanceDone()
                .endEvent()
                .done()

which generates

and the config looks fine:


Can you explain what"many more times" means. Provide very specific example please?

What does the collection you are sending into the subprocess “#{gettingLunch}” look like? You dont have the same user in the collection multiple times?

edit: you also have a typo in your “timesUpOrOrderComplete” task id and the .moveToNode("killUserTask").connectTo("timesUpOrOrderComplete") line. You had different spelling.

Sorry about that, I am using Kotlin, and some of the things I wrote are extension functions so I needed to ‘unpack’ them back into the Fluent Builder.

class ForumPost(override val key: String) : AbstractProcess() {

    override fun buildModel(processBuilder: ProcessBuilder): EndEventBuilder {
        return processBuilder
                .name("Lunch Process")
                .startEvent("lunchOps")
                .serviceTask("initializeGettingLunchList")
                .camundaClass(InitializeGettingLunchListDelegate::class.java)


                .subProcess()
                .embeddedSubProcess()
                .startEvent()

                // This just makes sure there is a 'placedOrder' in the process
                .serviceTask()
                .camundaClass(InstantiateOrderPlacedDelegate::class.java)

                //Assign them an order
                .userTask("placeOrders")
                .name("Place your order at: #{orderLink}")

                .camundaFormField()
                .camundaId("placedOrder")
                .camundaLabel("I placed my order!")
                .camundaType(UserFormType.BOOLEAN.type)
                .camundaDefaultValue("false")
                .camundaFormFieldDone()
                .camundaAssignee("#{lunchGetter.id}")


                .boundaryEvent("killUserTask")
                .timerWithDuration("PT1H")
                .cancelActivity(true)
                .moveToActivity<UserTaskBuilder>("placeOrders")

                .boundaryEvent()
                .timerWithCycle("R3/PT10M")
                .cancelActivity(false)
                .serviceTask("reminderIfNeeded")
                .camundaClass(OrderLunchReminderDelegate::class.java)
                .endEvent()
                .moveToActivity<UserTaskBuilder>("placeOrders")

                .serviceTask("timesUpOrOrderComplete")
                .name("timesUpOrOrderComplete")
                .implementation("expression")
                .camundaExpression("\${1 + 1}")

                .moveToNode("killUserTask").connectTo("timeUpOrOrderComplete")

                .endEvent()
                .subProcessDone()
                .multiInstance()
                .parallel()
                .camundaCollection("#{gettingLunch}")
                .camundaElementVariable("lunchGetter")
                .multiInstanceDone<SubProcessBuilder>()

                .endEvent()
    }
}

That is still not executable. There is no way that compiles :wink:

You still have the error i listed above:

edit: you also have a typo in your “timesUpOrOrderComplete” task id and the .moveToNode(“killUserTask”).connectTo(“timesUpOrOrderComplete”) line. You had different spelling.

@dan looking at the compiled code and bpmn file i posted above, it looks like your issue is likely with the collection you are injecting into the sub-process. Can you try with a single user being injected into the sub process? And just to confirm, you are only running a single engine? (not clustering or anything like that)

Thanks for the fixes, I missed that typo! (timesUpOrOrderComplete)


RE: “That is still not executable.”

I wonder what the differences are, I definitely have some additional support code, because sigh and I can’t believe I’m saying this, “it works(compiles) on my machine” :tired_face:

What are you using to assess the validity of this business process? Because I am running, line-for-line what was posted. I build this process and run it using the Spring Boot Starter skeleton. I’m not 100% sure if that changes the deployment to be multi-engine, I wouldn’t think so.

The list is a collection of users, I use it elsewhere in the codebase and am sure that it has a finite number of elements in it, but I’ll whittle it down to one and see what happens.


RE: “many more times”

Setup:
I have debug output in the OrderLunchReminderDelegate class to print out the current user.

Expected: I have 10 users in that collection, for a repeating reminder “R3/PT10M” I’d expect a total of 30 executions of that code. I should see the following three times: All 10 users in that list should be output.

Actual:
I’m seeing upwards of 500+ prints of my current user.

I see my first three users repeated and typically when the killUserTask hits then I see the rest of my users print out once. ex; 1 2 3 - 1 2 3 - 1 2 3 -…- 1 2 3 4 5 6 7 8 9 10

Can you export the bpmn xml text/string that is generated by your fluent API code. And share that bpmn file.

That might be prudent at this point in time. :grinning:

lunch.bpmn20.xml (13.4 KB)

OKay!

So see the problem.

I was able to recreate your issue with the several hundred instances of “reminder” being created on my first deployment.

There are a few problems going on here:

  1. The default job wait period of 1 min. see: Timer Execution too slow. Basically in your “test” you have your timers set for 30S and 10S which are too small for the default setup. But it does “feel” like a bug… where the job executor is racing with it self and creating hundreds of reminders. It feels like a uncaught exception/outlier condition @thorben;

  2. If you want short timers like this, look to modify the job executor settings

  3. The other problem is if you increase the killTask timer to say a 5min, but you set the reminder timer to 30sec, it is still possible the second or third repeats will be missed. I was able to recreate this in a quick little test. The way that the Timers / Business Calendars work is: A Timer is created with a due date whcih is stored as a Job. When that Job is executed, if it was a recurring date/timer it re-evaluates the timer logic and determines the next date. What is happening in this scenario of a short repeat is 1 or more cycles are being missed because by the time the job is executed, previously periods have already passed. @thorben might be able to explain or clarify this further.

So long story short: For your test if you increase your Kill Task timer to say 30min, and set your reminder to say 5 mins, you should have no issues.
You can also decrease the max wait period for the job executor (tell the job executor to look for new jobs on a more frequent basis) with the link above.

Ya seems like a uncaught scenario of job executor racing with it self:

Run 1:

Run 2:

The instances dont match the “expected” result because of the bad job executor config, but that sort of makes is “Execpted”.

The real issue here is that the issue seems to happen only on the first run of the process def after a deployment. On a second, third, fourth run, it does not occur.

@thorben

Simple BPMN for testing
lunch.bpmn20.bpmn (10.7 KB)

I had those set short for testing, I was shooting myself in the foot! :scream:

I can confirm that using 5M/1M timers works as expected. Wow, this is super helpful and informative. Thank you so much for sticking with me through this issue!

@thorben

Some more examples for showing the issue

Both are “first run” after a deployment (through rest api)

Exact same bpmn in all cases, no changes

@dan really easy to fix your test if you are using the spring boot. Just modify the job executor settings in the application.yaml file

You just mean to configure application.yml like

camunda:
  bpm:
    job-execution:
      enabled: false

For testing?

@dan see the specific comment: Timer Execution too slow

and its these settings:

camunda.bpm.job-execution

.wait-time-in-millis Specifies the wait time of the job acquisition thread in milliseconds in case there are less jobs available for execution than requested during acquisition. If this is repeatedly the case, the wait time is increased exponentially by the factor waitIncreaseFactor. The wait time is capped by maxWait. Default: 5000

.max-wait Specifies the maximum wait time of the job acquisition thread in milliseconds in case there are less jobs available for execution than requested during acquisition. Default 60000

.backoff-time-in-millis Specifies the wait time of the job acquisition thread in milliseconds in case jobs were acquired but could not be locked. This condition indicates that there are other job acquisition threads acquiring jobs in parallel. If this is repeatedly the case, the backoff time is increased exponentially by the factor waitIncreaseFactor. The time is capped by maxBackoff. With every increase in backoff time, the number of jobs acquired increases by waitIncreaseFactor as well. Default 0

.max-backoff Specifies the maximum wait time of the job acquisition thread in milliseconds in case jobs were acquired but could not be locked. Default 0

.backoff-decrease-threshold Specifies the number of successful job acquisition cycles without a job locking failure before the backoff time is decreased again. In that case, the backoff time is reduced by waitIncreaseFactor. Default 100

.wait-increase-factor Specifies the factor by which wait and backoff time are increased in case their activation conditions are repeatedly met.

https://docs.camunda.org/manual/latest/user-guide/spring-boot-integration/configuration/#camunda-engine-properties

1 Like

It would probably be simpler to just write a small unit test.

Here is a example of how you can test timer expectations:
See the following thread: Timer Evaluation: Unit Testing Timer configurations outside of execution If assuming you have the expected result, you can take the same logic put execute it as part of a actual unit tested execution.

It is using Spock Framework and groovy. But sure you can convert the same logic for kotlin if you like

1 Like

Thanks for the redirect, unit testing would be much more ideal! (Especially since it’d save me time in iterating). I love Spock! I might actually use that for my tests (I’m a big fan of using it when testing Optaplanner rules), since the Kotlin equivalent, Spek, doesn’t have Data Tables to the best of my knowledge. (see: http://engineering.pivotal.io/post/spek-data-driven-tests/)

@dan
Take a look at:

Should have all the spock examples you need :wink:

Can also take a look at: Rebuild alternative using Spock Framework · Issue #32 · camunda-community-hub/camunda-bpm-process-test-coverage · GitHub

Its currently in the bpmn-coverage branch of the camunda-spock-testing repo. Gives you fun coverage reports

Created issue: https://app.camunda.com/jira/browse/CAM-9265

1 Like