{"id":16454974,"url":"https://github.com/lucadibello/concurrency-spin-model-checker","last_synced_at":"2026-02-05T05:34:39.561Z","repository":{"id":236926153,"uuid":"793436052","full_name":"lucadibello/concurrency-spin-model-checker","owner":"lucadibello","description":"SPIN model checker integration to verify the correctness of a sequential and concurrent program","archived":false,"fork":false,"pushed_at":"2024-05-18T15:02:06.000Z","size":1920,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-16T11:21:34.729Z","etag":null,"topics":["concurrency","model-checking","promela","software-analysis","spin"],"latest_commit_sha":null,"homepage":"","language":"TeX","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lucadibello.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-29T08:13:00.000Z","updated_at":"2024-06-10T14:23:23.000Z","dependencies_parsed_at":"2024-05-18T15:25:13.069Z","dependency_job_id":"ac0757ab-53d8-4fd1-b0cc-6f74f7153eae","html_url":"https://github.com/lucadibello/concurrency-spin-model-checker","commit_stats":null,"previous_names":["lucadibello/concurrency-spin-model-checker"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/lucadibello/concurrency-spin-model-checker","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucadibello%2Fconcurrency-spin-model-checker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucadibello%2Fconcurrency-spin-model-checker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucadibello%2Fconcurrency-spin-model-checker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucadibello%2Fconcurrency-spin-model-checker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lucadibello","download_url":"https://codeload.github.com/lucadibello/concurrency-spin-model-checker/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucadibello%2Fconcurrency-spin-model-checker/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29113717,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-05T05:31:32.482Z","status":"ssl_error","status_checked_at":"2026-02-05T05:31:29.075Z","response_time":65,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["concurrency","model-checking","promela","software-analysis","spin"],"created_at":"2024-10-11T10:20:37.175Z","updated_at":"2026-02-05T05:34:39.544Z","avatar_url":"https://github.com/lucadibello.png","language":"TeX","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Model checking with SPIN\n\n## Introduction\n\nThis assignment examines the implementation of model checking using the\n[SPIN](https://spinroot.com/spin/whatispin.html) tool to verify the\ncorrectness of two versions of a frequency counter program: one\nsequential and the other parallel. Model checking is a technique that\nallows to verify the correctness of certain properties of a system\ndescribed in a finite-state model.\n\nThe following sections will discussed how the program has been modeled\nusing [ProMeLa](https://en.wikipedia.org/wiki/Promela) language, which\nLinear Temporal Logic (LTL) properties have been defined to verify the\ncorrectness of the program, and how the verification has been performed\nusing SPIN.\n\n## ProMeLa Model\n\nThe ProMeLa model consists of two main processes: the first process\nhandles the sequential computation of frequency counts, storing results\nin an array `sequential_counts`. The second process on the other hand,\ninitiates parallel computation by spawning a worker process for each\npossible value in the input array. These workers update an array\n`parallel_counts` concurrently without encountering race conditions as\neach worker updates a unique position in the array.\n\nAs explicitly stated in the assignment, the ProMeLa model presents two\nconstrants:\n\n1.  `MAX`: It represents the maximum value that can be assigned to an\n    element in the array. This constant is used as an upper bound when\n    filling the input array with random values.\n\n2.  `LENGTH`: the length of the input array. This constant is used many\n    times in the model to iterate over the input array while performing\n    various operations.\n\nThe model presents an `init` block that initializes the input array with\nrandom values between 0 and `MAX` and starts both the sequential and\nparallel processes. The code is available in code block below:\n\n```promela\n//\n// ProMeLa code\n//\n\n// Define the maximum number of elements in the array\n#define MAX 2\n#define LENGTH 2\n\n// Define the variables\nint a[LENGTH];\n\n// Keep track of result of both versions of the program\nint sequential_counts[MAX + 1];\nint parallel_counts[MAX + 1];\n\n// Entry point of the program\ninit {\n    // Initialize the array non-deterministically\n  printf(\"Random state:\\n\")\n    int i;\n    for (i : 0 .. LENGTH - 1) {\n        // Select a random value for the array\n        int v;\n        select(v : 0 .. MAX);\n        // Assign the value to the array\n        a[i] = v;\n\n    // Print the value\n    printf(\"\\ta[%d] = %d\\n\", i, v);\n    }\n\n    // Run the sequential version of the program\n  printf(\"Running sequential version...\\n\");\n    run sequentialCounter();\n\n  // Run the parallel version of the program\n  printf(\"Running parallel version...\\n\");\n  run parallelCounter();\n}\n```\n\nFrom the code above is possible to see that both versions of the program\nhave been started using the `run` keyword inside the same ProMela model.\nThis is different from the Java implementation, where the two versions\nwere implemented in separate classes. This design choice was essential\nto allow the verification of both versions of the program in the same\nmodel; otherwise, it would be impossible to perform verifications that\ncompare data yielded by two different simulations.\n\nIn the abstraction, the sequential and parallel processes are\nimplemented as separate processes. An in-depth analysis of each process\nis provided in the following sections.\n\n### Sequential Process\n\nThe sequential version of the frequency counter program is implemented\nin the `sequentialCounter` process. This process iterates over the input\narray and increments the corresponding position in the\n`sequential_counts` array.\n\nThe ProMeLa implementation of this version of the program is almost\nidentical to the Java implementation. This was expected, as both\nlanguages use a C-like syntax, and the logic of the program is simple.\nThe code is available in listing blow:\n\n```java\n//\n// Java code\n//\npublic int mostFrequent() {\n    int mostFrequent = -1;\n    int maxFrequency = -1;\n    int[] frequencies = new int[max + 1];\n    for (int k = 0; k \u003c a.length; k++) {\n        int value = a[k];\n        frequencies[value] += 1;\n        int frequency = frequencies[value];\n        if (frequency \u003e maxFrequency) {\n            mostFrequent = value;\n            maxFrequency = frequency;\n        }\n    }\n    return mostFrequent;\n}\n```\n\n```promela\n//\n// ProMeLa code\n//\nproctype sequentialCounter() {\n    int maxFrequency = -1;\n    int k;\n    for (k : 0 .. LENGTH - 1) {\n        int value = a[k];\n        sequential_counts[value] = sequential_counts[value] + 1;\n        if\n        :: sequential_counts[value] \u003e maxFrequency -\u003e\n            maxFrequency = sequential_counts[value];\n            sequential_result = value;\n        :: else -\u003e skip;\n        fi\n    }\n    // Signal that the sequential version is done\n    sequentialDone = 1;\n}\n```\n\nThe main difference between the Java and ProMeLa implementations is the\nway the program yields the result: in the Java implementation, the\nresult is simply returned by the method, while in the ProMeLa\nimplementation, the result is stored in a global variable\n`sequential_result`. This is necessary as the process cannot return a\nvalue.\n\n### Parallel Process\n\nThe parallel version of the frequency counter program is implemented in\nthe `parallelCounter` process. This process spawns `MAX + 1` worker\nprocesses that concurrently count the frequency of each value in the\ninput array. Each worker process is started with a unique value to\ncount, and by iterating over the input array, increments the\ncorresponding position in the `parallel_counts` array.\n\nAfter all worker processes have completed, the main parallel counter\nprocess iterates through the results and saves the value with the\nhighest frequency inside the `parallel_result` variable.\n\nTo detect when all worker processes have completed, a _channel_ is used\nto synchronize the main process with the workers. The channel is created\nin the main process and passed as an argument to each worker process. As\nsoon as a worker process completes, it sends its PID through the\nchannel. Since we start `MAX + 1` worker processes, we expect to read\n`MAX + 1` values from the channel. By leveraging this, the\n_parallelCounter_ process can detect when all worker processes have\ncompleted. The code is available in code block below.\n\n```promela\n//\n// ProMeLa code\n//\nproctype parallelCounter() {\n  // Create channel to wait for workers to finish\n  chan joinCh = [MAX + 1] of { pid };\n  // Create array of PID's for workers\n  pid workers[MAX + 1];\n  // Create a worker for each possible value\n  int i;\n  for (i : 0 .. MAX) {\n    workers[i] = run parallelWorker(i, joinCh);\n  }\n  // Wait for all workers to finish by reading from the channel\n  for (i : 0 .. MAX) {\n    int done;\n    joinCh ? done;\n  }\n  // If we are here, all workers are done as we read MAX+1 values from the channel\n  printf(\"\\t [!] all workers are done\\n\");\n  ...\n}\n```\n\nThis implementation differs from the Java implementation, where it does\nnot wait for all threads to complete but instead starts them all\ntogether and then waits for each thread individually to complete and\nsave its result. In the ProMeLa abstraction, we start the worker\nprocesses right away and use a channel to wait for all of them to\ncomplete before saving the actual result.\n\n#### Worker Process\n\nThe worker process has been implemented in the `parallelWorker` process.\nAs mentioned in the previous section, each worker process is started\nwith a unique value to count and increments the corresponding position\nin the `parallel_counts` array when it finds the value in the input\narray. The code is available in code block below.\n\nThe main difference between the two implementations is how the worker\nprocess is started. In the Java implementation, this required the\ncreation of a specific class that implements the `Runnable` interface,\nwhile in the ProMeLa implementation, this each worker is a process that\nis started by the main `parallelCounter` process.\n\n```java\n//\n// Java code\n//\nprotected int frequencyOf(int n) {\n    int frequency = 0;\n    for (int value: a) {\n      if (value == n)\n        frequency += 1;\n    }\n    return frequency;\n}\n\nclass ThreadedCounter extends SequentialCounter implements Runnable\n{\n     private int frequency;\n     private int n;\n\n     ThreadedCounter(int[] a, int n, int max) {\n          super(a, max);\n          this.n = n;\n     }\n\n     public void run() {\n          frequency = frequencyOf(n);\n     }\n\n     public int frequency() {\n          return frequency;\n     }\n}\n```\n\n```promela\n//\n// ProMeLa code\n//\n\n// The worker process represents a thread that looks for the count of a specific value in the array\nproctype parallelWorker(int value; chan out) {\n  // Look for the value in the array\n  int frequency = 0;\n  int i;\n  for (i : 0 .. LENGTH - 1) {\n    if\n      :: a[i] == value -\u003e frequency = frequency + 1;\n      :: else -\u003e skip;\n    fi\n  }\n  // Update the value in the parallel counts array\n  parallel_counts[value] = frequency;\n  printf(\"Worker for value %d is done\\n\", value);\n  out ! _pid;\n}\n```\n\nA notable implementation detail is that the Java worker, using an\nexternal function named _frequencyOf_, only computes the frequency of a\nspecific value. In contrast, the ProMeLa worker process not only\ncomputes this frequency but also saves the result in the\n`parallel_counts` array and synchronizes with the parent process using\nthe channel.\n\n### Parameters: MAX and LENGTH\n\nIf MAX and LENGTH are too high, the model checking process can take a\nlong time to complete, or even run out of memory. To analyze the\nbehavior of the model checking process with different values of MAX and\nLENGTH, I first run the checker by keeping one parameter fixed and\nvarying the other. This has been done for both parameters to understad\ntheir impact on the model checking performance. The results can be seen\nin the following figure:\n\n![Model checking execution time with different values of MAX and\nLENGTH](./report/images/max_length_exec_time.png)\n\nThen, to have a better understanding, I also decided to increase the\nvalues of both MAX and LENGTH to understand how the model checking\nprocess behaves. Then, I merged the results in a single plot, available\nin the figure below:\n\n![Model checking execution time with increasing values of MAX and\nLENGTH](./report/images/max_len_increase_exec_time.png)\n\nFrom the plot, it is possible to see that the model checking process is\nfast enough when the values of MAX and LENGTH are extremely low\n($\\leq 2$). However, as the values of MAX and LENGT increase, the model\nchecking process becomes explonentially slower. This is expected, as the\nmodel checking process has to explore all possible states of the system,\nand the number of states grows exponentially with the values of MAX and\nLENGTH.\n\nAfter the benchmarks outlined above, has been decided to set the valued\nof MAX and LENGTH to 2, to keep the model checking process fast and\nefficient enough to be able to analyze the behavior of the model.\n\n## LTL Properties\n\nIn the following subsections, I will discuss the LTL properties that\nhave been defined to satisfy the requirements of the assignment. I\ndeveloped\n\n### Verify completition of both sequential and parallel processes\n\nTo be able to verify the completition of both the sequential and\nparallel processes, I decided to use two global variables:\n`sequentialDone` and `parallelDone`. These variables are set to 0 by\ndefault, and set to 1 when the respective process has completed. To\nverify the completition of both processes, I defined the following LTL\nproperty:\n\n```promela\nltl termination { \u003c\u003e (sequentialdone == 1 \u0026\u0026 paralleldone == 1) }\n```\n\n### Same most frequent value\n\nTo ensure that both versions of the frequency counter give the same\nresult, I defined the following LTL property:\n\n```promela\nltl sameResult { [] (sequentialDone == 1 \u0026\u0026 parallelDone == 1) -\u003e (sequential_result == parallel_result)}\n```\n\nThis LTL verifies that the result of both versions (sequential and\nparallel) is the same after both processes have completed. This is\nimportant property to verify, as the two algorithms are expected to\nyield the same result.\n\n### Sum of frequencies\n\nAs we are interested in verifying the correctness of the frequency\ncounter program, I decided to add an additional LTL properety that\nensures that the sum of the frequencies of all values in the\n`sequential_counts` and `parallel_counts` arrays is equal to the length\nof the input array. This property is defined as follows:\n\n```promela\nltl sumCounts { [] (sequentialDone == 1 \u0026\u0026 parallelDone == 1) -\u003e (sumCountsSequential == LENGTH \u0026\u0026 sumCountsParallel == LENGTH) }\n```\n\nTo verify this property I had to add two \\\"counter\\\" variables,\\\n`sumCountsSequential` and `sumCountsParallel`, that are computed by\nsumming the frequencies of all values in the respective arrays right\nbefore completing the respective processes. This way, I can verify that\nthe sum of the frequencies is equal to the length of the input array.\nWithout these variables, it would be impossible to verify the total sum\nof the frequencies.\n\n### Invalid LTL formula: partial result\n\nAs requesed by the assignment, I also added on purpose an invalid LTL\nformula that SPIN will not be able to verify. The property I decided to\nadd is the following:\n\n```promela\nltl alwaysSameResult { [](sequential_result == parallel_result) }\n```\n\nThis property is very similar to the `sameResult` property,\nbut it does not take into account the completition of the processes.\nThis LTL is wrong, as the two results are\nnot expected to be the same while the processes are still running.\n\nThis is because the two versions of the program are not executed in\nparallel (first the sequential version and then the parallel version)\nand also, the two versions work in different ways. The sequential\nversion computes the result and updates the `sequential_result` variable\nas soon as it finds a value with a higher frequency. The parallel\nversion, on the other hand, spawns multiple worker processes that\nconcurrently compute the frequency of each value in the input array,\nand, only after all workers have completed, the main process updates the\n`parallel_result` variable.\n\nFor these multiple reasons, the `alwaysSameResult` property is invalid\nand SPIN will find a counterexample right after the first iteration of\nthe sequential process. This is the counterexample that SPIN will find\n(truncate for brevity):\n\n```text\n    #processes: 2\n                    a[0] = 0\n                    a[1] = 0\n                    sequential_counts[0] = 1\n                    sequential_counts[1] = 0\n                    sequential_counts[2] = 0\n                    parallel_counts[0] = 0\n                    parallel_counts[1] = 0\n                    parallel_counts[2] = 0\n                    sequential_result = 0\n                    parallel_result = -1\n                    sequentialDone = 0\n                    parallelDone = 0\n                    sumCountsSequential = 0\n                    sumCountsParallel = 0\n     41:    proc  1 (sequentialCounter:1) ../src/promela/model.pml:51 (state 12)\n     41:    proc  0 (:init::1) ../src/promela/model.pml:42 (state 22)\n     41:    proc  - (notSameResult:1) _spin_nvr.tmp:60 (state 6)\n```\n\nFrom the output above, it is possible to understand why, where and when\nSPIN found the counterexample: the LTL property is violated right after\nthe first iteration of the sequential process as the two results are\ndifferent (`sequential_result` = 0 and `parallel_result` = -1). At the\nmoment ot the violation, the parallel process has not even started yet.\n\n## SPIN model checker via script\n\nTo automate the verification process, has been created a script that\nwill build the ProMeLa model, compile the resulting analyzer using\n`gcc`, and run the model checker using SPIN for each of the defined LTL\nproperties. To run the script, it is possible to use the\nprovided Makefile target `run` or run the script directly. Use the\ncommands below to run the script:\n\n```bash\n      make run\n      # or\n      ./scripts/run-model.sh\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucadibello%2Fconcurrency-spin-model-checker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flucadibello%2Fconcurrency-spin-model-checker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucadibello%2Fconcurrency-spin-model-checker/lists"}