Well, threading is a major problem area, but it is most definitely broken by design in any case, in that it has sort-of "spooky action at a distance" semantics, which is just completely uncomposable. Imagine you call a function from a library that simply scans a directory for files with a specific magic value, say ... if any other part of your program happens to hold a POSIX lock on any of those files (could even be another unrelated library), that will release those locks. That's just braindead.
I can't claim to speak for the designers, but my intuition (having had my first Unix exposure in the early/mid-80s) is that their assumptions were along the lines that it would be used consistent with the part of the Unix philosophy stating:
> Make each program do one thing well. To do a new job, build afresh rather than complicate old programs by adding new "features".
This seems inconsistent with the (now totally common) programming styles that use multiple, unrelated libraries or have a single program do something that requires keeping files with locks open, while going off on a tangent to scan a directory. Back then, I suspect the standard practice would be to fork() if not outright delegate the task to existing tools like find/xargs.
I certainly agree that hidden side effects like closing a duplicate fd causing all the locks to disappear is undesirable, but it might not even have occurred to them that anyone would care. Maybe they thought about it and the alternative would be too memory intensive (remember when memory was actually scarce?). It would be interesting if anyone had pointers to something like actual mailing list archives.
Characterizing it as "just braindead" as uncharitable, when it's decades later, without at least being able to point to contemporaneous pressures and constraints.
ATT’s implementation had this really stupid behavior and they snuck it into POSIX because that meant they wouldn’t have to change their existing code. No one else realized how stupid it was until it was too late.
> The reason is historical and reflects a flaw in the POSIX standards process, in my opinion, one that hopefully won't be repeated in the future. I finally tracked down why this insane behavior was standardized by the POSIX committee by talking to long-time BSD hacker and POSIX standards committee member Kirk McKusick (he of the BSD daemon artwork). As he recalls, AT&T brought the current behavior to the standards committee as a proposal for byte-range locking, as this was how their current code implementation worked. The committee asked other ISVs if this was how locking should be done. The ISVs who cared about byte range locking were the large database vendors such as Oracle, Sybase and Informix (at the time). All of these companies did their own byte range locking within their own applications, none of them depended on or needed the underlying operating system to provide locking services for them. So their unanimous answer was "we don't care". In the absence of any strong negative feedback on a proposal, the committee added it "as-is", and took as the desired behavior the specifics of the first implementation, the brain-dead one from AT&T.
That's the first design-related documentation I've seen presented, so thank you.
However, it's merely an indictment of the POSIX standards process and sheds no light on why the AT&T implementation was the way it was in the first place.
I keep reading what, to me (essentially an outsider with no horse in the race), sound like either hyperboles at worst ("brain-dead", "really stupid", "no excuse") and arguments advocating the MIT Method (of "Worse is Better" fame) at best.
It tends to beg the question, "If it's so horrible, why did anyone bother to implement it that way or use it once it was there?" I'd expect, if it's actually as broken as everyone makes it out to be, that it would be worse than nothing and would get no use.
Apparently, the major db vendors didn't bother, at least at the time, but that could well be because there was no reliable [1] cross-platform option.
So, again, how about some actual, contemporaneous evidence of the original design process, for a fair, contextual critique?
[1] i.e. reliable or good enough, not reliable in the sense of implementation correctness covering all corner cases, as seems to be demanded by certain commenters and the MIT Method in general.
What would falsify your position? Is there any evidence that you would not reject with "but maybe it was good enough"?
If I presented a case where a worker was killed because of this locking mechanism, wouldn't you just say "but maybe it prevented the meltdown of a nuclear reactor, so it was a net positive vs. no locking at all, therefore, it was the right thing to do"?
I'm not sure you actually understand my position, which has nothing to do with which philosophy is "right" or "better", but, rather, that the one you seem to object to so strenuously on moral grounds, both existed and was a valid engineering consideration/strategy at the time.
To falsify it, you would merely need documentation that such a philosophy was not a consideration in this design.
If it's falsified, then there may well be something new to be learned about design and mistakes to be avoided.
Otherwise, it's just another example of "Worse Is Better" and isn't worth the effort.
A hacky implementation can be simultaneously good enough for some users and also completely unacceptable for a standard.
It's not worse than nothing, sure. If you use it exactly as expected, and only have one logical user of a file in each process, it does an acceptable job. But in the context of what it's supposed to do, be a generic locking mechanism, it's horrifically broken.
So I'll summarize this way: It's completely okay that they wrote this code, as a "version 0.2". But there's no excuse for presenting it as finished code, with reasonable semantics. It's not hyperbole to say that.
> It's completely okay that they wrote this code, as a "version 0.2". But there's no excuse for presenting it as finished code, with reasonable semantics. It's not hyperbole to say that.
Am I missing something, or are you just saying "Worse is Worse"?
The "excuse" is that this way (arguably, perhaps) governed the history of Unix development, well before there was even a standard.
What I've been attempting to get people (especially ones with the seemingly most strenuous invectives toward the design) is to perform the thought experiment of placing themselves in the "shoes" of the designers, both by trying to imagine being in that past and, much harder, actually believing in that philosophy.
I believe that will go much farther in increasing understand and, to borrow from the HN guidelines, gratify intellectual curiosity, than arguing against strawmen (or just non-existent proponents) of design goodness.
> Am I missing something, or are you just saying "Worse is Worse"?
No, I'm not. It's fine that they made that code, and were using it.
But different situations have different requirements.
I'm not objecting to the design work at all.
I'm objecting to the idea of calling it "ready to standardize". This is a presentation problem, not a development problem. It was half-baked, and shouldn't have been set in stone until it was fully baked.
> I'm objecting to the idea of calling it "ready to standardize".
Oh, indeed, that's a distinctly different topic than has been focused on in the rest of the thread. The upthread indictment of the process is quite on point.
It's also a wide topic with a wide variety of involved parties, worthy of its own thread, off of a blog post.
> both existed and was a valid engineering consideration/strategy at the time.
Please define what you mean by "valid".
> To falsify it, you would merely need documentation that such a philosophy was not a consideration in this design.
Well, then I probably don't care? I care whether it was a bad idea, not whether it was a bad idea coming from a bad philosophy or a bad idea standing on its own.
> Well, then I probably don't care? I care whether it was a bad idea, not whether it was a bad idea coming from a bad philosophy or a bad idea standing on its own.
If your goal is to prevent such a "bad idea" in the future, then not caring could easily work against you.
You'd end up having to expend energy challenging each bad idea on its own, possibly failing even to make inroads because there's a bad philosophy that makes it seem like a good idea (maybe even obviously so, rendering your challenge easily dismissed and a wasted effort).
Instead, if you focus your energy on challenging the bad philosophy, you both get to the heart of the matter right away, and you cover all the new bad ideas it enables all at once (and even ahead of time). You also won't have to start from scratch, as the philosophy, unlike every new bad idea, isn't unique, and there's likely plenty of literature out there already that you can use as ammunition.
Well, yeah, of course I care. But not for the determination of whether a bad idea is bad. This may be an instance of worse is better, but it is a bad idea regardless, and thus at best an additional piece of evidence against the philosophy.
Eeeh, this seems like a far fetched interpretation of the principle. The dbm library, for one, was around since '79,and I'd guess Ken Thompson would adhere to the principles instead if they were so pervasive.
I wasn't asserting that they were pervasive or even universal (in the Unix universe).
Perhaps I'm missing your point about dbm, unless it's just that libraries existed, even long ago, that happened to open files. That doesn't refute my point, which is that a process operating on those files via that library and outside of that library is incongruous.
As further evidence for there being at least an idea of such a dual "class" of files, I present the fact that the * glob does not match files starting with a dot.
Well, take a simpler case: A program that takes a lock and then reads a file specified by the user on the command line.
There are tons of perfectly reasonable scenarios that are hilariously complicated to implement correctly, even ones that fit within the more traditional unix approach.
Yes, maybe this is a result of some sort of tradeoff, but I would say it almost certainly was not a sensible tradeoff even back then. I don't see how using a sensible strategy could be much worse in terms of any resources used (I mean, really, it's a matter of where you put the pointers to the lock structures and where you call the automatic unlock handler), and the costs of using the semantics as they are now correctly are so high it's just not a useful abstraction really. If anything, this smells like one of those tradeoffs where a terrible abstraction that is incredibly difficult to use correctly was chosen simply because it was less code to write, while thus causing every user of the abstraction to have to write tons more code if they want to have correct code.
Though if you ask me this looks much more like someone not thinking it through. Locks are for coordinating concurrency, the unit of concurrency is the process, so locks are held by processes. Oh, and we have to clean up locks that aren't released by the process! Well, let's do that when the file is closed ... OK, done!
> Well, take a simpler case: A program that takes a lock and then reads a file specified by the user on the command line.
I'm having trouble seeing the use case under that Unix Philosophy constraint. I admit I might not be imaginative enough, but a program/utility that would be normally expected to operate on its own locked file (by being asked to do so by the user) still seems incongruous.
> it was less code to write, while thus causing every user of the abstraction to have to write tons more code if they want to have correct code.
Actually, that's a completely valid design decision and philosophically compatible, even if you happen to find it distasteful.
It's one of the (caricatured) tenets of "Worse is Better" which has been credited with the (original) success of Unix over more "correct" competitors http://dreamsongs.com/RiseOfWorseIsBetter.html :
> Simplicity -- the design must be simple, both in implementation and interface. It is more important for the implementation to be simple than the interface. Simplicity is the most important consideration in a design.
> Correctness -- the design must be correct in all observable aspects. It is slightly better to be simple than correct.
> Consistency -- the design must not be overly inconsistent. Consistency can be sacrificed for simplicity in some cases, but it is better to drop those parts of the design that deal with less common circumstances than to introduce either implementational complexity or inconsistency.
> Completeness -- the design must cover as many important situations as is practical. All reasonably expected cases should be covered. Completeness can be sacrificed in favor of any other quality. In fact, completeness must be sacrificed whenever implementation simplicity is jeopardized. Consistency can be sacrificed to achieve completeness if simplicity is retained; especially worthless is consistency of interface.
It seems like advisory locking has made a "worse" sacrifice in each category, in favor of the purported benefit. There's even a reference (though I don't recall if it's in that essay, specifically) to making the user do extra coding work for correctness.
Of course, there are plenty of critics of "Worse is Better" or the "New Jersey approach", including the author of that essay. However, his whole point is that it improves survivability, which, arguably, was still important to Unix in the 80s and maybe even into the 90s.
> not thinking it through. Locks are for coordinating concurrency, the unit of concurrency is the process, so locks are held by processes. Oh, and we have to clean up locks that aren't released by the process! Well, let's do that when the file is closed
It's absolutely not thinking every possible future scenario and corner case through, but that, I would argue, is the opposite of "braindead". It worked, especially for the use case you described, which seems perfectly reasonable for early Unix (and adherents to the Unix Philosophy of any era).
> I'm having trouble seeing the use case under that Unix Philosophy constraint. I admit I might not be imaginative enough, but a program/utility that would be normally expected to operate on its own locked file (by being asked to do so by the user) still seems incongruous.
That's just bad engineering. "Seems unlikely, therefore we ignore it" is essentially how you create broken corner cases that result in unreliable systems, i.e., braindead.
> Actually, that's a completely valid design decision and philosophically compatible, even if you happen to find it distasteful.
No, it's not. It's one that people make, that doesn't make it valid, just a reality.
> However, his whole point is that it improves survivability, which, arguably, was still important to Unix in the 80s and maybe even into the 90s.
That's not a justification for building bad systems, that's only an explanation for why bad systems survive once they have been built.
You might as well say that fraud improves survivability. Yeah, under certain circumstances it does, that doesn't mean that winning a market via fraud is in the interest of all market participants or that your product is therefore good, it simply means that you can win against competitors by using fraud.
> It's absolutely not thinking every possible future scenario and corner case through, but that, I would argue, is the opposite of "braindead".
It's obviously wrong scoping, hence braindead. You don't need to think through every possible future scenario, you simply have to try and formulate the composability semantics of the mechanism and you should notice it's broken.
> It worked, especially for the use case you described, which seems perfectly reasonable for early Unix (and adherents to the Unix Philosophy of any era).
> That's just bad engineering. "Seems unlikely, therefore we ignore it" is essentially how you create broken corner cases that result in unreliable systems, i.e., braindead.
I can only assume, then, that your "simple" example wasn't simple at all, but a contrivance to result in such a corner case.
You've also confused "we ignore it" with "we consciously decide not to bother with it".
> That's not a justification for building bad systems, that's only an explanation for why bad systems survive once they have been built.
This is a distinction without a difference. This isn't a moral issue, so "justification" is irrelevant.
There are only tradeoffs. Insisting that the design was just bad, broken, unreliable, or braindead, while not even acknowledging the tradeoffs (i.e. ignoring the positives or, instead, calling them negatives) strikes me as disingenuous.
> You might as well say that fraud improves survivability.
Nope. That's a strawman.
> In which case that philosophy was braindead then?
I urge you to read the essay and the commentary around "Worse is Better". Otherwise, all this is essentially just a shallow dismissal.
You have, in essence, argued in favor of the "MIT method". As I said, this argument was already given, by the original proponents, the mentioned essay's author, and numerous other reasonably well known names in computer science, mostly decades ago. A web search or even just Wikipedia can provide jumping off points.
Wut? Explanations are the same thing as justifications? I think you have to explain ...
> This isn't a moral issue, so "justification" is irrelevant.
Actually, it is?
> There are only tradeoffs. Insisting that the design was just bad, broken, unreliable, or braindead, while not even acknowledging the tradeoffs (i.e. ignoring the positives or, instead, calling them negatives) strikes me as disingenuous.
There probably were no benefits, other than for the people implementing the kernel, as explained earlier.
> Nope. That's a strawman.
So, what am I misrepresenting then?
> I urge you to read the essay and the commentary around "Worse is Better". Otherwise, all this is essentially just a shallow dismissal.
Yeah, thanks, it doesn't make sense as a justification, only as an explanation.