The magic file approach described by kevincox below is probably the best way to get > 95% of the benefit with < 1% of the work.
587 karma · joined August 16, 2022
The magic file approach described by kevincox below is probably the best way to get > 95% of the benefit with < 1% of the work.
How does the baseline filesystem watcher work? If it doesn't use kqueue, does it poll the filesystem periodically and diff against an in memory representation? If yes, see my other comments. If not, I am genuinely curious what you are doing because you know something that I do not.
For what it's worth, in spite of ned14's comments, I have never seen inotify fail in practice (except for if it hits the os file descriptor limits in which case it does fail noisily). The tool I wrote uses inotify for linux. It is used by thousands of developers every day as part of an editor integration and there are no open issues about dropped file events.
Your time frame is probably about right. It took me about a year to work through all the edge cases.
Option 1 uses a lot of CPU and memory (the map storing the paths being monitored could easily grow to be tens or even hundreds of megabytes if many files are being monitored, which is often the case in large source projects). I have seen tools that use polling with a 100ms interval continuously burn 50% of cpu monitoring a modest sized directory with tens of thousands of files.
Option 2 theoretically would use less memory and little to no cpu, but in practice, the story is more complicated. If you are using an inotify or kqueue like api, you will have to store handles for all of the paths that are being monitored, which can take a significant amount of memory. On macos, the file system events are not accurate in the sense that you can't trust the type of event. It doesn't reliably distinguish between creation and modification events. So if you want to know specifically what kind of event happened, you end up back in case 1 where you have to store an in memory representation of the file system and diff against the in memory representation and the current file system state when you detect an event. For some use cases, you may not care to distinguish between creations and modifications and can get away with a lower memory, but less accurate, solution.
In my experience, getting all of this right is much more difficult than it appears at first glance. Good luck to you.
The other fun part is that there is often a lag between the deletion and the create in the text editor case so it is necessary to defer triggering events when a deletion is detected and wait a little while to see if there is a corresponding creation. Otherwise you may rerun your command that depends on the file before it exists.
It is possible to get like a 99+% solution to this problem without polling but it is a lot harder than what these simple tools, including entr do. The upshot is that file monitoring should be looked at as a lowest common denominator solution. A better solution is to build automatic command running into the text editor itself.
No one wants cowboy pilots ignoring ground control. Doctors though do not exactly have the best historical track record.
Knowledge communities should indeed work towards consensus and constantly be trying to improve themselves. Consensus though is not always desirable. Often consensus goes in very, very dark directions. Even if there is some universal best practice for some particular problem, my belief is that codifying certain things as "best practice" and policing the use of alternative strategies is more likely to get in the way of actually getting closer to that platonic ideal.
If someone did want to carve into SQLite's embedded db monopoly, it would take years to develop a comparable test suite. This seems possible, particularly if they develop a more expressive language for expressing the solutions to the types of problems that we use SQLite for. Who would fund this work though when SQLite works as well as it does?
For me, the concept of best practices is pernicious because it is a delegation of authority to external consensus which inevitably will lead to people being treated as tools as they are forced to contort to said best practices. The moment something becomes best practice, it becomes dogma.
The funny thing about this comment is that SQLite is as close to the gold standard of software quality that we have in the open source world. SQLite is the only program that I've ever used that reliably gets better with every release and never regresses. Could it be that this is precisely because they don't use "modern tools" and accept outside contributions?
One of the key differentiators for lua is that it has multiple implementations that are both rock solid and will work on essentially any computer that you are likely to ever work with. Luajit is api compatible lua 5.1 (with a few 5.2 features backported). Lua 5.1 came out in 2006, which is to say that when writing lua 5.1 code, you are targeting a language that hasn't really changed in 15 years. This is incredibly liberating because you can trust that any code that you write in luajit or lua 5.1 is likely to work essentially forever (modulo security issues of course).
Lua is also one of the most simple languages out there. Pretty much everything that you need to know is in the reference documentation, which is a single file that you can easily download and store for offline use. It also hasn't changed in a decade and there is no reason to expect it will in the future.
Simplicity begets simplicity. As simple as lua is, you can make it even simpler by restricting yourself to a limited subset of the language. The more you restrict yourself, paradoxically the more liberated you are. You eventually realize that you can express any computational problem in lua. Lua provides an implicit vm that serves as an idealized model of a computer. It has numbers, strings, arrays and associative arrays (with a uniform syntax for both types of arrays). This is enough to do pretty much anything without too much pain. The lua runtime itself is fast enough to do most programming tasks faster than you can perceive. For the remaining tasks, you can just implement them in c and call them from lua. But you won't be writing normal c, you will be writing your own dialect of c that itself reflects lua. Many of the problems of c, particularly around memory management, are avoidable when the c code is explicitly designed to be embedded inside of a lua program.
Continuing on the theme of simplicity, lua is also one of the fastest scripting languages with respect to startup time (EDIT apologies for the poor formatting below but console output doesn't work great with HN):
bash-5.1$ time lua -e 'print "Hello, world!"' Hello, world!
real 0m0.004s user 0m0.002s sys 0m0.002s
bash-5.1$ time python3 -c 'print("Hello, world!")' Hello, world!
real 0m0.034s user 0m0.024s sys 0m0.007s
bash-5.1$ time node -e 'console.log("Hello, world!")' Hello, world!
real 0m0.062s user 0m0.049s sys 0m0.011s
bash-5.1$ time ruby -e 'puts "Hello, world!"' Hello, world!
real 0m0.086s user 0m0.062s sys 0m0.021s
The lua startup time is actually comparable to a standalone c executable:
bash-5.1$ time printf '#include <stdio.h>\nint main() { puts("Hello, world!"); return 0; }' | gcc -x c -o a.out - && ./a.out "on mac os, there are some checks that are run the first time a new exectuable is called so do one dry run before timing the results" > /dev/null; time ./a.out
real 0m0.074s user 0m0.050s sys 0m0.021s
Hello, world!
real 0m0.002s user 0m0.001s sys 0m0.001s
Comparing startup time may seem silly, but it raises the question how exactly you are benefiting from the excess complexity relative to lua that those other language runtimes have. You also can exploit the fast startup time to create a low latency development experience with minimal tooling. Configure your editor to save lua files with every keystroke. Then in a shell window, run something like: > ls foo.lua | entr -c time lua foo.lua
You might be amazed by how much you can do in a lua script before the the latency becomes noticeable. By timing the results, you are also immediately aware of the impact on performance that any change to the script might have. This enables you to have an entirely different kind of development experience in which you are always fully aware of the state of your program because you are never waiting for compilation. The program either does what you expect it to, or it doesn't. No time is spent in the liminal state where one suspects the code may be correct (or incorrect), but you aren't really sure. You start realizing that if you organize your code in a certain way, you almost never have to wait more than say 100ms to get feedback.
On top of all that, if you don't like the syntax of lua, you can write a parser for another language in pure lua. Your new language, which will transpile to lua, will automatically inherit the good properties of the lua runtime.
Mastering lua will completely change the way that you think as a programmer and has the potentially to radically improve your experience of programming. Of course, once you have mastered lua the next step is to go beyond lua...
The same could equally well be said for, say, scala. In my experience though, you always pay one way or another for the unneeded and unwanted features in a language. Unreasonable compile times that kill joy and productivity are just one of the ways.
Admittedly, this is a somewhat far fetched scenario that I have posited but I am somewhat concerned that the proliferation of incorrect tree-sitter grammars is going to lead to the worst kind of problem down the road: tools that work great _most_ of the time.
Agreed. The cosmopolitan project is incredible hacking and I appreciate the author's ingenuity, but this has always felt a little bit to me like a solution in search of a problem. How is cosmopolitan software intended to be distributed? If the answer is on the internet, I don't see how a single fat binary helps the end user dramatically more than separate download links. For the developer, cross compilation can be automated as part of deployment. The platform specific binaries should both be smaller than the universal ape binaries and won't resort to hackery to work properly.
I understand where you are coming from but I will state unequivocally: people are not _just_ inherently greedy. Each of us has the capacity to be greedy and selfish, but we also each have the capacity to be giving, caring and accountable to others. How might we act if we lived in a society in which wealth hoarding was a cultural taboo rather than a celebrated cultural value? What if we looked towards billionaires with pity rather than envy or resentment?
As the late anthropologist, David Graeber, put it: “The ultimate hidden truth of the world is that it is just something we make, and could just as easily make something different.” Rather than just concede that capitalism is the best we can possibly do, why not consider how we could reform it and/or replace it with something better? (For the record, I consider communism and capitalism two sides of the same coin. We need to be more creative and visionary about the world we want to live in than what these tired ideologies offer.)
From the car's perspective, the current law is great. Many people who would consider alternative transportation such as cycling or walking are discouraged by the fact that these activities are much more dangerous for you than driving. If you get hit by a reckless SUV at 40mph while you are in a car, you have a pretty good chance of surviving. Your chances of surviving as a pedestrian or cyclist are drastically lower. Rationally, many people then opt for the car (for me, must American roads are too dangerous for cycling so I only walk to get around even though I do enjoy cycling). This is a vicious cycle that ensures that cars will perpetually dominate our streets. I fear self driving cars (or an infinite series of failed attempts at self driving cars) are only going to make this worse. For all of the benefits that cars do offer us in our daily lives, from my perspective, it seems clear that the scales have tipped way too far in favor of the car.
Different legal regimes are possible. For example, we could change the law so that by default drivers are assumed responsible for any collision they were a part of. In the case of a driver killing a pedestrian, the burden should fall on the driver to prove that it was the pedestrians fault. The current enforcement of the law seems to almost imply that pedestrians and cyclists are suicidal so if they die in a collision, it must have been their fault somehow. Surely we could require drivers to drive in such a way that they have very low probability of injuring anyone? Or would that be an unimaginable infringement on the rights of the driver (and car)?
The beauty of a system that actually holds drivers accountable for the consequences of their actions is that we actually wouldn't have much need to enforce laws like speeding and running red lights because few would want to take the risk of losing their license if they were involved in a crash.