{"id":18020934,"url":"https://github.com/edubart/forkmon","last_synced_at":"2025-03-26T22:30:31.866Z","repository":{"id":49884652,"uuid":"367652427","full_name":"edubart/forkmon","owner":"edubart","description":"Watch for file changes and auto restart an application using fork checkpoints to continue the process (for quick live development)","archived":false,"fork":false,"pushed_at":"2021-12-30T12:33:24.000Z","size":201,"stargazers_count":13,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-22T14:22:05.939Z","etag":null,"topics":["fork","forkmon","inotify","live-coding","livereload","monitor","nelua"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edubart.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-05-15T14:28:58.000Z","updated_at":"2024-10-25T12:41:40.000Z","dependencies_parsed_at":"2022-09-02T20:22:22.085Z","dependency_job_id":null,"html_url":"https://github.com/edubart/forkmon","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edubart%2Fforkmon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edubart%2Fforkmon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edubart%2Fforkmon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edubart%2Fforkmon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edubart","download_url":"https://codeload.github.com/edubart/forkmon/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245747386,"owners_count":20665780,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["fork","forkmon","inotify","live-coding","livereload","monitor","nelua"],"created_at":"2024-10-30T06:08:19.323Z","updated_at":"2025-03-26T22:30:31.427Z","avatar_url":"https://github.com/edubart.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Forkmon\n\nWatch for file changes and auto restart an application using fork checkpoints to continue. Intended for quick live development.\nThis **works only on Linux systems**.\n\n## Quick Usage\n\nSmall example of compiling `forkmon` and using with Lua to reload scripts\non the middle:\n\n```sh\ngit clone https://github.com/edubart/forkmon.git \u0026\u0026 cd forkmon\nmake\nalias forkmon-lua='LD_PRELOAD=`pwd`/forkmon.so FORKMON_FILTER=\"%.lua$\" lua'\nforkmon-lua tests/example.lua\n```\n\nNow when running the above you should get something like:\n\n```sh\n[forkmon] watch '/home/bart/projects/forkmon/example.lua'\nstartup\n[forkmon] watch '/home/bart/projects/forkmon/foo.lua'\nfoo!\nfinished\n```\n\nThe application will keep running, waiting for any watched file change.\nNow try to edit to `foo.lua` and change the print to `'hello world!'`, you should get something like:\n```sh\n[forkmon] file '/home/bart/projects/forkmon/tests/foo.lua' changed, resuming from it..\nhello world!\nfinished\n```\n\nNotice that only `'hello world!'` is printed, but not `'startup'`,\nthis means the application did not restart from beginning, but from\nthe middle instead!\nBecause a forked (cloned) application was waiting for changes in the `foo.lua` and resumed.\n\n## How it works\n\nThe Linux `fork()` function has the interesting property of cloning\na child process when called, the child process memory remains the same\nas the parent process, however both can run independently, each one with its own state and memory. But  this is not a heavy operation,\nthe process memory is not duplicated, instead\n[copy on write](https://en.wikipedia.org/wiki/Copy-on-write) is used,\nthus this is a lightweight operation.\nThis property of `fork()` allows to us\nto create checkpoints of the program that we can later use to resume a\nnew process from the checkpoint without require the application to\nstart from beginning, effectively rolling back state and memory in time.\nIf we hook all `fopen()` calls we can make checkpoints\nevery time a file is opened,\nthen using [inotify Linux API](https://en.wikipedia.org/wiki/Inotify) we can watch files for\nchanges and once a file change is detected we can resume a new process\nfrom its checkpoint instead of restarting the whole application.\nThis allows to gain a few startup time in live development scenarios.\n\n## Options\n\nThe tool can be configured using the following environment variables:\n\n* `FORKMON_FILTER` pattern that a watched file name should match, following [Lua pattern rules](https://www.lua.org/manual/5.4/manual.html#6.4.1),\nmultiple filter patterns can be used when using the `;` separator.\n* `FORKMON_QUIET` if set, the tool will be quiet and not print anything.\n* `FORKMON_NO_COLORS` if set, no colors will be used in terminal output.\n* `FORKMON_RESTART_DELAY` how many milliseconds to wait before restarting the application when a file has changed (default 50).\nMust be more than 0, so the OS can properly flush all file changes.\n* `FORKMON_IGNORE_DELAY` how many milliseconds to ignore new changes\nafter a file has been changed (default 200).\nMust be more than 0, so when saving files in a batch does not trigger\nmany restarts.\n## Motivation\n\nI had this idea other day when thinking in ways to speedup the [Nelua](https://nelua.io/) compiler, this tool can be used there to skip redundant compiler work.\nBecause usually when you edit a source file the\ncompiler needs to go parsing all sources again, even things before\nthe source file change. This tool is a \"hacky\" way to\nallow the compiler to skip parsing and analyzing everything\nbefore a source file change. And if the sources are designed\nin such a way that a separate single source file requires all hardly\never changing files (similar to precompiled headers in C world),\nthen the compilation can be much faster by\nskipping lots of parsing.\n\nAlthough this was made for quick live development with Nelua on Linux,\nit can be used to speedup other console applications\nor compilers. And probably even servers\nthat goes through a lot of loading and processing during startup,\nthough the server startup need to be designed in such way that the\ncheckpoints places does not have networking or multithreading going on.\n\n## Limitations\n\n* Only works well with application where all state\nto checkpoint is available in CPU memory, such as\nsingle threaded console applications.\nThis is not the case for networking, graphical or multithreading applications, to make it work in such\nkind of applications extra work would be needed.\n* The application must use `open`, `fopen` or `fopen64` to open files,\nas these are the only file opening functions hooked. Some applications\nuses the `openat` syscall and this case will be missed.\n* The application should not launch sub processes, thus this\ntool does not work well with GCC/Clang compilers as they launch itself\nwhen compiling, however it works with TCC compiler thus TCC could be even faster for live development!\n* File descriptor offset are shared between processes (a `fork()` behavior),\nand this can be problematic for applications that keep files open\ninstead of caching them in memory.\n\n## Troubleshooting\n\nIn case you get the error `[forkmon] failed to initialize inotify` then\nis probably because too many inotify instances are active,\nthen try to increase this limit with `sudo sysctl -w user.max_inotify_instances=1024`\nor kill any zombie processes of the application (in edge cases can happen).\n\n## Implementation details\n\nThis has been implemented using the [Nelua](https://nelua.io/)\nprogramming language,\nhowever a standalone C file is bundled in the repository,\nthus just a C compiler is needed to compile the project.\n\n## License\n\nMIT License, see LICENSE\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedubart%2Fforkmon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedubart%2Fforkmon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedubart%2Fforkmon/lists"}