|
| 1 | +# Concurrency |
| 2 | + |
| 3 | +It's a small library that simplifies dividing N tasks between X workers with but two options: |
| 4 | +* payload |
| 5 | +* early exit on first error |
| 6 | + |
| 7 | +It was created for parallelising chain interaction with [Seth](../libs/seth.md), where strict ordering |
| 8 | +and association of a given private key with a specific contract is required. |
| 9 | + |
| 10 | +> [!NOTE] |
| 11 | +> This library is an overkill if all you need to do is to deploy 10 contract, where each deployment |
| 12 | +> consists of a single transaction. |
| 13 | +> But... if your deployment flow is a multi-stepped one and it's crucial that all operations are executed |
| 14 | +> using the same private key (e.g. due to privilleged access) it might be a good pick. Especially, if |
| 15 | +> you don't want to extensively test a native `WaitGroup`/`ErrGroup`-based solution. |
| 16 | +
|
| 17 | +## No payload |
| 18 | +If the task to be executed requires no payload (or it's the same for each task) using the tool is much simpler. |
| 19 | + |
| 20 | +First you need to create an instance of the executor: |
| 21 | +```go |
| 22 | +l := logging.GetTestLogger(nil) |
| 23 | + |
| 24 | +executor := concurrency.NewConcurrentExecutor[ContractIntstance, contractResult, concurrency.NoTaskType](l) |
| 25 | +``` |
| 26 | + |
| 27 | +Where generic parameters represent (from left to right): |
| 28 | +* type of execution result |
| 29 | +* type of channel that holds the results |
| 30 | +* type of task payload |
| 31 | + |
| 32 | +In our case, we want the execution to return `ContractInstance`s, that will be stored by this type: |
| 33 | +```go |
| 34 | +type contractResult struct { |
| 35 | + instance ContractIntstance |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | +And which won't use any payload, as indicated by a no-op `concurrency.NoTaskType`. |
| 40 | + |
| 41 | +Then, we need to define a function that will be executed for each task. For example: |
| 42 | +```go |
| 43 | +var deployContractFn = func(channel chan contractResult, errorCh chan error, executorNum int) { |
| 44 | + keyNum := executorNum + 1 // key 0 is the root key |
| 45 | + |
| 46 | + instance, err := client.deployContractFromKey(keyNum) |
| 47 | + if err != nil { |
| 48 | + errorCh <- err |
| 49 | + return |
| 50 | + } |
| 51 | + |
| 52 | + channel <- contractResult{instance: instance} |
| 53 | +} |
| 54 | +``` |
| 55 | + |
| 56 | +It needs to have the following signature: |
| 57 | +```go |
| 58 | +type SimpleTaskProcessorFn[ResultChannelType any] func(resultCh chan ResultChannelType, errorCh chan error, executorNum int) |
| 59 | +``` |
| 60 | +and send results of successful execution to `resultCh` and errors to `errorCh`. |
| 61 | + |
| 62 | +Once the processing function is defined all that's left is the execution: |
| 63 | +```go |
| 64 | +results, err := executor.ExecuteSimple(client.getConcurrency(), numberOfContracts, deployContractFn) |
| 65 | +``` |
| 66 | + |
| 67 | +Parameters for `ExecuteSimple` (without payload) are as follows(from left to right): |
| 68 | +* concurrency count (number of parallel executors) |
| 69 | +* total number of executions |
| 70 | +* function to execute |
| 71 | + |
| 72 | +`results` contain a slice with results of each execution with `ContractInstance` type. |
| 73 | +`err` will be non-nil if any of the executions failed. To get all errors you should call `executor.GetErrors()`. |
| 74 | + |
| 75 | +## With payload |
| 76 | +If your tasks need payload, then two things change. |
| 77 | + |
| 78 | +First, you need to pass task type, when creating the executor instance: |
| 79 | +```go |
| 80 | +executor := concurrency.NewConcurrentExecutor[ContractIntstance, contractResult, contractConfiguration](l) |
| 81 | +``` |
| 82 | + |
| 83 | +Here, it's set to dummy: |
| 84 | +```go |
| 85 | +type contractConfiguration struct{} |
| 86 | +``` |
| 87 | + |
| 88 | +Second, the signature of processing function: |
| 89 | +```go |
| 90 | +type TaskProcessorFn[ResultChannelType, TaskType any] func(resultCh chan ResultChannelType, errorCh chan error, executorNum int, payload TaskType) |
| 91 | +``` |
| 92 | +Which now includes a forth parameter representing the payload. And that function's implementation (making use of the payload). |
| 93 | + |
| 94 | +> [!NOTE] |
| 95 | +> You can find the full example [here](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/concurrency/example_test.go). |
0 commit comments