[crates.io] Disable yanking crates that have dependant crates?


#1

I just encountered an issue on crates.io that is very reminiscent of the leftpad debacle.

The problem is that chrono 0.3.1 has been yanked, while the MongoDB crate has 2 recent releases (0.2.9 and 0.3) that have become completely unusable as a result. Any other crate releases that depend on any of these 3 will also be broken now.

The core of the issue as I see it at this time is that it was possible to yank the 0.3.1 release in the first place. For the record, I’m not assigning any blame here, to anyone. I think it was an honest mistake, made possible by a systemic gap I’d like to see closed (i.e. the ability to yank actually-used crate releases from crates.io).
Furthermore I can see that yanking a release can be useful if the release is broken somehow.
But I also think that it’s safe to say that if another crate depends on a given crate release, that that release most likely will work, by which I mean compile successfully. Either that, or neither dependency nor dependent crate was actually tested, in which case the question should be “what’s it doing on crates.io?” IMO.

The solution I’m thinking of is to remove the ability of crate owners to yank a release that is actively used “in the wild” i.e. if it has other crates that depend on it. The reasoning here is a basic utilitarian approach, combined with the fact that the crate owner actively consented to the crate’s public availability by the act of publishing it in the first place.

I’ve mainly started this thread to get the discussion going, but I also am looking for other opinions on this, so please have at it :slight_smile:


#2

Yanking is fine, but you should only be able to yank if there’s a semver compatible version that can be chosen instead. So in case of chrono there should’ve been a 0.3.2 before yanking 0.3.1.

Also discord-rs is dealing with the breakage issue too from what I’ve seen.


#3

Ok so I got the heuristic somewhat wrong :stuck_out_tongue:

But I’m still convinced that selectively disallowing yanking is an essential step in order to have long-term crate stability in the Rust ecosystem.


#4

Yanking is also different from deleting. chrono 0.3.1 still exists and if your project depends on it, will download and compile just fine. Cargo just won’t create a new dependency to it. The only reason it broke here is that there was no pre-existing dependency to chrono 0.3.1 in the Cargo.lock, so Cargo had to create a new dependency to it. So any project with chrono 0.3.1 in its Cargo.lock should still work just fine.


#5

Ok that’s useful for locations that incidentally already have it installed.
But I was just doing a new clone for my project on a different OS, so that’s not going to help me in this case unfortunately.
And regardless, as a crate user I would still call this situation “unstable” (not in the rustc sense but more in the general sense of the word), as I cannot rely on the crate being downloadable and useable from anywhere anymore.
Additionally, it seems to me that even something as innocuous and simple as cargo update after upping the version of a dependency in Cargo.toml can thus irreversibly break your local project.


#6

No it’s still downloadable for all projects that have committed their Cargo.lock. But yeah, it’s definitely not a good situation right now.


#7

I’m the author of discord-rs and was affected by the yank. I think yanking the version is the appropriate action when publishing it in the first place was a mistake. v0.3.1 broke compatibility with v0.3.0 in violation of semver. Whether the crate was yanked or not, someone would have their builds break by running cargo update (either incorrect upgrade from v0.3.0 to v0.3.1 or incorrect downgrade from v0.3.1 to v0.3.0). Releasing a v0.3.2 after yanking v0.3.1 does not mitigate the problem in this case (though it is important if a serious bug is being corrected rather than a semver violation).


#8

I think valid arguments can be made both ways for whether or not chrono should’ve yanked 0.3.1, since it was a semver violation as understood by many in the Rust community (I think technically it wasn’t, but we tend to be stricter with 0.x releases than semver specifies). Long term I don’t think it would’ve mattered, really, since new crates aren’t going to be depending on 0.3 anyways.

I think a good takeaway is that a major/minor bump to one of your crate’s dependencies should trigger a corresponding major/minor bump in your crate version, unless you’re really sure it isn’t a public dependency (chances are it is though).

That’s sort of off-topic though. I’m not sure introducing a scheme for determining whether or not a crate can be yanked is really worthwhile, because there are valid reasons to yank a crate even if it it’s a dependent. But I can see your point about a scenario where a prominent crate is yanked and there’s no clear resolution.


#9

Odd, I have gotten the exact opposite impression from the rust community i.e. pre-1.0.0 releases are somewhat “anything goes”. After you hit 1.0.0 though, normal semver semantics apply.

I agree fully with your second point, if a publicly exported dependency changes a minor or major version (as opposed to a patch version), then it’s likely that dependent crates need to do that as well. Perhaps a lint could be written for it? I’m not exactly sure where the limits of what can be linted are at the moment.

As for your last point: my main concern here is reliability: if I can’t be sure that some (transient) dependency won’t be available in 3 years or even half a year, then people like me can’t… no refuse to build anything with it that is supposed to be reliable, as the maintenance burden is wildly unpredictable and can thus easily outweigh any potential benefits Rust might offer. And all of this at the whims of another individual somewhere in the ecosystem who may or may not be a malicious actor in the sense of repeating the whole leftpad debacle. It’s basically the reason why it is a really bad idea to use NodeJS in production, despite its popularity and other virtues.


#10

Note that crates.io still has the crate files for yanked crates, it’s only cargo’s resolution algorithm that will refuse to create new dependencies on yanked crates. So this is not like the leftpad situation.

I disagree; there are many places where this situation could have gone differently. I don’t know enough details about this situation, but in general here are some alternate scenarios that would have prevented this:

  • The mongo DB crate[*] could have had a looser version dependency on chrono, for example 0.3 rather than 0.3.1. Then when 0.3.1 was yanked, new dependencies on the mongo DB crate would have fallen back to 0.3.0. I see other commenters mention that chrono 0.3.1’s API was not semver compatible with 0.3.0. Because of that, perhaps mongo DB depended on APIs that only existed in 0.3.1 and that’s why the version was specified this way. The root cause here would then be chrono publishing a version that didn’t follow semver, not the ability to yank. There’s a Google Summer of Code project to create a tool that could validate a library’s API correctly follows semver that would be a more appropriate solution in this case.
  • chrono should have released a 0.3.2 before yanking 0.3.1, as others have mentioned. One could argue that this is inappropriate, though, since 0.3.1 should have been 0.4.0 based on the changes to the API. The solution to that is for crates depending on the new APIs in 0.3.1 to depend on 0.4.0, and then perhaps yank their versions that depended on 0.3.1.

Some more thoughts:

But I was just doing a new clone for my project on a different OS, so that’s not going to help me in this case unfortunately.

Is your project a binary? If so, you should have Cargo.lock checked in to version control, which would have prevented this.

Additionally, it seems to me that even something as innocuous and simple as cargo update after upping the version of a dependency in Cargo.toml can thus irreversibly break your local project.

cargo update will not start depending on a yanked crate; I’m not sure why you have the impression that cargo update could break your project. Is there something I’m missing?

we tend to be stricter with 0.x releases than semver specifies

Just want to clarify that the “we tend to be stricter” here isn’t only “the Rust community tends to be socially stricter”, it’s “cargo’s version resolution is looser than what semver specifies because of the social strictness”. See the specifying dependencies docs, namely this part:

While SemVer says that there is no compatibility before 1.0.0, many programmers treat a 0.x.y release in the same way as a 1.x.y release: that is, y is incremented for bugfixes, and x is incremented for new features. As such, Cargo considers a 0.x.y and 0.x.z version, where z > y, to be compatible.

Regarding public/private dependencies:

unless you’re really sure it isn’t a public dependency (chances are it is though).

This RFC is being discussed by the cargo team. We’re still working through a few related ideas to make sure we’ve thought through all the interactions.

[*] I noticed the Mongo DB crate’s repository is named mongo-rust-driver-prototype; I think the name “prototype” adequately sets expectations that this might break, so I’m a bit confused at your level of concern here.


#11

As the co-maintainer/co-author of the mongodb crate, I think I can provide a bit of insight, as I think that mongodb is in a bit of a different situation from most other crates.

First, though, I think it’s important to note that both chrono and and mongodb are pre-1.0, meaning all the caveats about a lack of stability apply. I assume that chrono is being audited as a part of the ongoing Rust libz blitz due to its wide usage in the ecosystem, so hopefully we’ll see fewer of these types of things in the future, but I think it’s important to be aware of the possibility of hiccups like this happening.

As for mongodb specifically, the issue arose out of the fact that the mongodb crate is tightly coupled with the bson crate, but two two crates have different owners. The reason for this is that the bson crate predates the writing of the MongoDB driver, and we didn’t feel that there was a strong reason to write our own library rather than work with the author of the bson crate. My co-author was given write access to the bson repository, but does not have publish access for the crate. Because of this, he was able to update the dependency in bson on Github, but we couldn’t fix the driver until the original author published a new version of bson. I think can be solved by asking the original author of bson to grant my co-author publish access for the bson crate, but I’d understand if the author is reluctant to do so, and I’m not super fond of the idea of forking bson at this point, as we’ve worked quite well with them since first contacting them.

Because of the somewhat unique circumstances surrounding mongodb's relationship with bson, I’m not convinced that there’s an underlying issue with the Cargo/crates.io ecosystem but more of a social kink in the way bson and mongodb are maintained, which we’ll need to think about going forward.

A quick note on the name of the repo for mongodb, i.e. mongo-rust-driver-prototype: this is more of a MongoDB thing than a Rust thing. My co-author and I originally wrote the driver when we interned at MongoDB, so although we both still maintain it, it’s still owned by MongoDB. Given that the driver was written by people who were working at MongoDB at the time, that I’m currently working at MongoDB full-time (on the C++ driver though, not the Rust driver), and that the driver is owned by MongoDB and hosted on a MongoDB-owned repository, we want to be perfectly clear that while the mongodb crate supports MongoDB (the database), it is not officially supported by MongoDB (the company). Given that all the repositories of the drivers officially supported by MongoDB (the company) are named “mongo-[lang]-driver” (e.g. “mongo-cxx-driver”, “mongo-ruby-driver”), we just appended “prototype” to the name of the Rust driver repository to indicate that it is not a “MongoDB officially supported driver”. If at some point MongoDB (the company) decides to start officially supporting a Rust driver, the driver’s repository will be named “mongo-rust-driver”.

tl;dr pre-1.0 crates are by definition unstable, coordination between authors of multiple crates can take a couple of days, Cargo/crates.io wasn’t really the cause of the delay for the fix, and the version number of mongodb is what indicates that it’s unstable, not the name of the repository

EDITED: Fixed a grammatical error, clarified part of the tl;dr, and added the word “not” to “I’m not super fond” because I accidentally said the opposite of what I meant


#12

Other people had already pointed out the 0.x version number and its semantics. The reason I pointed out the name was that it’s an additional signal. Furthermore, semantic versioning is only meant to convey the stability of the public API. Other signals like the use of the word “prototype” can indicate whether or not library does what you expect it to at all. There are plenty of 0.x crates used successfully in production because they work; the only meaning one is supposed to infer from the version number is whether the public API has backwards compatibility guarantees or not.

Mostly, I agree with you, disallowing yanking of versions that have crates depending on them would not have prevented this problem.


#13

I’m sorry, I didn’t mean to imply at all pre-1.0 software isn’t stable or functional or to ignore or criticize any part of this discussion. My main point in that paragraph (which I admit was hastily written and could have been clearer) was that the word “prototype” is an artifact of the way that MongoDB names its driver repositories and shouldn’t be taken as an indication of anything other than the fact that MongoDB (the company) doesn’t officially support the driver; you’re certainly correct I shouldn’t have mistakenly mentioned the version number when making this point.


#14

@carols10cents
Indeed, I agree that there are a number of circumstances that needed to come together for this to manifest.
Yet it did, which means to me that if coordination doesn’t structurally increase somehow*, this is likely to happen again somewhere in the ecosystem.

The project I was referring to was a library, thus no Cargo.lock file.
Another project that uses the first as a dependency however, indeed is a binary and thus has a lock file.

Cargo may not start depending on a yanked crate, but IIRC cargo will still remove the old/yanked dependency during a cargo update (if not with direct dependencies then with transitive dependencies where the direct dependency in and of itself is ok other than depending on a yanked dependency), which is breaking in and of itself since you can’t (easily) get it back in there. Hand-hacking the lock file (or any generated file at all) is a big no-no to me.

As for MongoDB, the crate itself may have the term prototype in there but it’s proven relatively stable.
I’m also not using it in production (luckily!), otherwise that would have caused more issues than it actually has now.

My real problem here is that this whole thing has shown me how easily things can break i.e. rust the language may be quite stable and safe, but unfortunately its crate ecosystem currently is not.
This has long-term maintenance implications: if a crate stays put once it’s uploaded, any consuming crate can rely on that dependency being there in the future.
If however, a crate can magically disappear from one moment to the next**, that has implications for long-term crate maintenance of nightmarish proportions in the worst case (and I’m always one for at least looking at the worst case).

Now, it’s obvious to me that this wasn’t malicious. But the next time it very well could be (e.g. an angry developer yanking all his/her much-used crate releases), and then it really would be leftpad all over again.
THAT is the issue I want to see fixed.

@sagm:
Thank you for the clarification.

*without having to increase communication at the same time obviously, since that is fairly expensive. Thus I’m thinking some kind of automated solution to this. What exactly it ends up being is less important than that it is automated.
** This whole story has proven that that is possible.


#15

I don’t understand why cargo doesn’t offer a flag to override its prejudice against yanked crates and allow it to generate a lockfile that has a yanked crate. The yanked crates are still there and projects that already have a lockfile can depend on them, so the fact that cargo refuses to create a lockfile depending on yanked crates with no way to override that is frustrating for people trying to get an old project to build. You can even put a giant big warning on my screen when I use that flag, I don’t care, I just want to be able to build some ancient project. Make the flag really long if it helps: --please-let-me-use-yanked-crates-i-understand-it-is-a-bad-idea. Just provide such a flag already.


#16

This sounds like a bug. Could you report it in cargo’s repo with steps to reproduce please?


#17

Is there an issue in cargo’s repo for this feature request?


#18

There is now.