Deny simultaneously workflow execution / Don't execute if workflow is running

Hey!

Is there a way to block workflow triggering when a workflow is still running? Other solution would be, that a workflow can only execute one by one.

In my scenario a signal is sent to a webhook trigger. Problem: Sometimes the signal is sent two times in a row and I want to ignore the second signal. At the moment the workflow gets perfectly executed two times.

What I tried so far:

  • Webhook gets triggert
  • readBinary reads timestamp of last execution and writeBinary writes the new timestamp
  • function checks difference between last timestamp and new time

Problem: Workflow execution is to fast. There are only milliseconds between the executions and during this time the binary is not changed.

Is there any other way to achieve that the second execution is getting blocked while the first one is running?

Because the readBinary/writeBinary combination is not fast enough I will try my luck with the staticData variable.

I combined it with an IF statement (if timestamp diffrence > 20000 = true), hopefully it’s fast enough. If someone also needs that kind of thing here is what I use at the moment (will be able to give feedback tomorrow…). I’m a little bit afraid that this is still not fast enough, but I can’t control/send the signals on my own so I have to wait :wink:

{
  "nodes": [
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{ $node[\"timestampQuery2\"].json[\"timeDifference\"] }}",
              "operation": "larger",
              "value2": 20000
            }
          ]
        }
      },
      "name": "checkTimestampDif",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        570,
        1240
      ]
    },
    {
      "parameters": {
        "functionCode": "const staticData = getWorkflowStaticData('node');\nconst timeNow = new Date().getTime()\nconst lastExecution = staticData.lastExecution;\nconst timeDifference = Number(timeNow) - Number(lastExecution)\nstaticData.lastExecution = timeNow;\nreturn [{json: {timeDifference}}]\n"
      },
      "name": "timestampQuery2",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        420,
        1240
      ]
    }
  ],
  "connections": {
    "timestampQuery2": {
      "main": [
        [
          {
            "node": "checkTimestampDif",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Hey David.

Interesting approach using the static data, but checking n8n’s source, the static data gets saved only at the end of the execution.

This means that if the signal is fired twice nearly at the same time, you can still have concurrency kicking in and letting two executions happen simultaneously.

Would it be ok to use external tools like a database system or Redis to assist controlling this concurrency?

Man, you are right. That’s pitty. I found two other solutions and decided to use the redis solution because the read/calculate/write process takes about 0,022 seconds which is fast enough.

The other possible solution I tried is to make a separate workflow which is only responsible for the webhook trigger, read global static data, calculate, write static data and execute the other workflow (execute workflow node. In this case the workflow executes quite fast and the global static data is stored fast enough - round about 0,050-0,060 seconds). Kind of a proxy workflow.

The redis solution is a little bit less dirty I would say :wink:

if someone is looking for a dirty redis solution here is some example code:

{
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "operation": "set",
        "key": "test1_lastExecution",
        "value": "={{$node[\"createExecutionTime1\"].json[\"timestampNow\"]}}",
        "keyType": "string"
      },
      "name": "redis_write",
      "type": "n8n-nodes-base.redis",
      "typeVersion": 1,
      "position": [
        1200,
        740
      ],
      "credentials": {
        "redis": "redisServer"
      }
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{ $node[\"createExecutionTime1\"].json[\"difNowAndLastExecution\"] }}",
              "operation": "larger",
              "value2": 15000
            }
          ]
        }
      },
      "name": "IF1",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1350,
        750
      ]
    },
    {
      "parameters": {
        "functionCode": "var timestampNow = Date.now();\nif ($node[\"redis_get\"].json[\"LastExec\"] != \"\") {\n  timestampLast = $node[\"redis_get\"].json[\"LastExec\"]\n} else {\n  timestampLast = '1519129853500'\n}\ndifNowAndLastExecution = Number(timestampNow) - Number(timestampLast);\nreturn [{json: {timestampNow, timestampLast, difNowAndLastExecution}}]\n\n\n\n\n\n\n\n"
      },
      "name": "createExecutionTime1",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        1050,
        740
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "propertyName": "LastExec",
        "key": "test1_lastExecution",
        "options": {}
      },
      "name": "redis_get",
      "type": "n8n-nodes-base.redis",
      "typeVersion": 1,
      "position": [
        900,
        740
      ],
      "credentials": {
        "redis": "redisServer"
      }
    }
  ],
  "connections": {
    "redis_write": {
      "main": [
        [
          {
            "node": "IF1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "createExecutionTime1": {
      "main": [
        [
          {
            "node": "redis_write",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "redis_get": {
      "main": [
        [
          {
            "node": "createExecutionTime1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

nice question on incoming http requests. to give you the technical name to what you want is “idempotent”

https://docs.mulesoft.com/mule-runtime/4.3/idempotent-message-validator

@David You can have a look at my example workflow here:

1 Like

Hey David.

Yes, using Redis is great. In order to guarantee atomicity it would be even better if we could use the INCR operation.

It is still not available on n8n but I just created a PR with this feature. Here: Adds increment operation to Redis by krynble · Pull Request #1745 · n8n-io/n8n · GitHub

The INCR operation increments a key by 1 unless another number is given. It also allows you to set an expiry timer.

If the key does not exist, it creates the key and assigns 1 to it. It also always returns the created key.

So you could use something like INCR semaphore and only proceed with the workflow execution if the result is 1.

Here is an example on Redis:

Screenshot from 2021-05-05 12-02-56

Should be released soon and can grant you the necessary atomicity of executions.

1 Like

The “Increment” operation for Redis got released with [email protected]

1 Like