{"thread":{"id":"54526","subject":"Credential Store: Don't acquire lock when only reading the file","startedAt":"2020-10-29T19:20:45Z","lastAt":"2020-10-30T15:01:39Z","messageCount":7,"participants":["Simão Afonso","Jeff King"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"408654","messageId":"20201029192020.mcri76ylbdure2o7@safonso-t430","threadId":"54526","inReplyTo":null,"subject":"Credential Store: Don't acquire lock when only reading the file","fromName":"Simão Afonso","fromEmail":"simao.afonso@powertools-tech.com","sentAt":"2020-10-29T19:20:20Z","receivedAt":"2020-10-29T19:20:45Z","isPatch":false,"sender":{"key":"simao.afonso@powertools-tech.com","avatar":"https://avatars.githubusercontent.com/u/49308688?v=4"},"body":"When git is configured for parallel fetches `fetch.parallel = 0` and\nuses the `store` credential helper, fetching all remotes might lead to a\nspurious lock fail. It's a race condition when several remotes require\nthe credentials at the same time.\n\nThis shouldn't happen, using the `get` operation should not lock the\nfile.\n"},{"id":"408718","messageId":"20201030055541.GA3264588@coredump.intra.peff.net","threadId":"54526","inReplyTo":"20201029192020.mcri76ylbdure2o7@safonso-t430","subject":"Re: Credential Store: Don't acquire lock when only reading the file","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-30T05:55:41Z","receivedAt":"2020-10-30T05:55:44Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Oct 29, 2020 at 07:20:20PM +0000, Simão Afonso wrote:\n\n> When git is configured for parallel fetches `fetch.parallel = 0` and\n> uses the `store` credential helper, fetching all remotes might lead to a\n> spurious lock fail. It's a race condition when several remotes require\n> the credentials at the same time.\n> \n> This shouldn't happen, using the `get` operation should not lock the\n> file.\n\nI agree that \"get\" should not be taking a lock. And looking at the code,\nI don't think that it is.\n\nHowever, after successfully using a password, Git will then trigger each\nhelper with a \"store\" command. So likely what you are seeing is each\nfetch telling credential-store to store the successful password.\n\nThere are a few options here:\n\n  - we could have Git not do that. And indeed, I wrote a patch for that\n    long ago:\n\n      https://lore.kernel.org/git/20120407033417.GA13914@sigill.intra.peff.net/\n\n    but it turns out that some people rely on it. There were some\n    options discussed there for moving it forward, but nothing ever\n    happened.\n\n    Another option that we didn't discuss there: we could remember which\n    helper gave us the credential and not feed it back to itself. That\n    would let simple cases work without the extra request, but would let\n    more complex ones (feeding the result of one helper to another)\n    continue to work the same.\n\n  - the problem with `store` is that it has unfriendly concurrency\n    semantics. Only one instance can hold the lock at a time, and\n    clients which see that the lock is held just fail immediately, even\n    though they could probably succeed by waiting a few milliseconds.\n    Whereas other helpers are likely a bit smarter about this. So if we\n    just wanted to improve credential-store, some other options are:\n\n      - teach it to try the lock until hitting a timeout. I think just\n\tswapping out hold_lock_file_for_update() for\n\thold_lock_file_for_update_timeout() would do it (I probably\n\twould have used it back then, but it didn't exist yet).\n\n      - teach it to check whether the requested update is a noop\n\t(because we already have the identical entry in the file)\n\twithout holding the lock. This implies an extra pass over the\n\tfile when we _do_ write something, but the noop case is clearly\n\tgoing to be the more common one (and will also be faster,\n\tbecause we won't do any writing at all).\n\n\tI think I do prefer handling this inside Git, though (i.e.,\n\thaving it realize it would be a noop and avoid calling the\n\thelper at all).\n\nInterested in trying a patch for any of those?\n\n-Peff\n"},{"id":"408730","messageId":"20201030112229.3rcyylkuyrlrhn7g@safonso-t430","threadId":"54526","inReplyTo":"20201030055541.GA3264588@coredump.intra.peff.net","subject":"Re: Credential Store: Don't acquire lock when only reading the file","fromName":"Simão Afonso","fromEmail":"simao.afonso@powertools-tech.com","sentAt":"2020-10-30T11:22:29Z","receivedAt":"2020-10-30T11:22:35Z","isPatch":false,"sender":{"key":"simao.afonso@powertools-tech.com","avatar":"https://avatars.githubusercontent.com/u/49308688?v=4"},"body":"Hi Jeff, thanks for the fast reply.\n\nOn 2020-10-30 01:55:41, Jeff King wrote:\n> I agree that \"get\" should not be taking a lock. And looking at the code,\n> I don't think that it is.\n> \n> However, after successfully using a password, Git will then trigger each\n> helper with a \"store\" command. So likely what you are seeing is each\n> fetch telling credential-store to store the successful password.\n\nAh, the plot thickens. That sounds more like it.\n\nOn 2020-10-30 01:55:41, Jeff King wrote:\n> There are a few options here:\n> \n>   - we could have Git not do that. And indeed, I wrote a patch for that\n>     long ago:\n> \n>       https://lore.kernel.org/git/20120407033417.GA13914@sigill.intra.peff.net/\n> \n>     but it turns out that some people rely on it. There were some\n>     options discussed there for moving it forward, but nothing ever\n>     happened.\n> \n>     Another option that we didn't discuss there: we could remember which\n>     helper gave us the credential and not feed it back to itself. That\n>     would let simple cases work without the extra request, but would let\n>     more complex ones (feeding the result of one helper to another)\n>     continue to work the same.\n\nRight, I did not imagine you could chain credential helpers, but that\nmakes sense.\n\nOn 2020-10-30 01:55:41, Jeff King wrote:\n> Interested in trying a patch for any of those?\n\nSounds good to me, I should be able to handle it if you CC/BCC me\n(subscribing to the firehose is a bit too much).\n\nFor reference, I'm using:\n\n> $ git --version\n> git version 2.29.1\n"},{"id":"408731","messageId":"20201030112524.GA3274373@coredump.intra.peff.net","threadId":"54526","inReplyTo":"20201030112229.3rcyylkuyrlrhn7g@safonso-t430","subject":"Re: Credential Store: Don't acquire lock when only reading the file","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-30T11:25:24Z","receivedAt":"2020-10-30T11:25:27Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Oct 30, 2020 at 11:22:29AM +0000, Simão Afonso wrote:\n\n> On 2020-10-30 01:55:41, Jeff King wrote:\n> > Interested in trying a patch for any of those?\n> \n> Sounds good to me, I should be able to handle it if you CC/BCC me\n> (subscribing to the firehose is a bit too much).\n\nI meant: would you like to try writing a patch. :)\n\nThe offer applies to anybody else, too, of course. I may work on it\neventually, but I've successfully left that series at the bottom of my\ntodo list for 8 years, so...\n\n-Peff\n"},{"id":"408733","messageId":"20201030113945.3edylqczqav2xd2k@safonso-t430","threadId":"54526","inReplyTo":"20201030112524.GA3274373@coredump.intra.peff.net","subject":"Re: Credential Store: Don't acquire lock when only reading the file","fromName":"Simão Afonso","fromEmail":"simao.afonso@powertools-tech.com","sentAt":"2020-10-30T11:39:45Z","receivedAt":"2020-10-30T11:39:52Z","isPatch":false,"sender":{"key":"simao.afonso@powertools-tech.com","avatar":"https://avatars.githubusercontent.com/u/49308688?v=4"},"body":"On 2020-10-30 07:25:24, Jeff King wrote:\n> I meant: would you like to try writing a patch. :)\n> \n> The offer applies to anybody else, too, of course. I may work on it\n> eventually, but I've successfully left that series at the bottom of my\n> todo list for 8 years, so...\n\nAh, that's another kettle of fish. I can try to take a look at the\nsimpler fixes you mentioned.\n"},{"id":"408735","messageId":"20201030120540.dna7k3bmnrrw4cck@safonso-t430","threadId":"54526","inReplyTo":"20201030055541.GA3264588@coredump.intra.peff.net","subject":"Re: Credential Store: Don't acquire lock when only reading the file","fromName":"Simão Afonso","fromEmail":"simao.afonso@powertools-tech.com","sentAt":"2020-10-30T12:05:40Z","receivedAt":"2020-10-30T12:05:50Z","isPatch":false,"sender":{"key":"simao.afonso@powertools-tech.com","avatar":"https://avatars.githubusercontent.com/u/49308688?v=4"},"body":"On 2020-10-30 01:55:41, Jeff King wrote:\n>       - teach it to try the lock until hitting a timeout. I think just\n> \tswapping out hold_lock_file_for_update() for\n> \thold_lock_file_for_update_timeout() would do it (I probably\n> \twould have used it back then, but it didn't exist yet).\n\nSo I tried to patch the credential store with that timeout function and\nit seems to solve it! Thanks.\n\nhttps://github.com/git/git/blob/v2.29.2/builtin/credential-store.c#L61\n\nThis is easily reproduced if you do a `fetch --all` in parallel on the\nsame repository (seen on the attached image):\n\n> $ for n in $(seq 100); do git fetch --all; sleep 0.5; done\n\nAbout the timeout, I put it at infinite retries, but should this be\nconfigurable? Or should it be a different default?\n\nThere aren't many timeout configurations. I found\n\"core.filesRefLockTimeout\" and \"core.packedRefsTimeout\", doesn't sound\nlike something that should be re-used.\nShould it be something like this with a different name?\n"},{"id":"408749","messageId":"20201030150136.GA3282152@coredump.intra.peff.net","threadId":"54526","inReplyTo":"20201030120540.dna7k3bmnrrw4cck@safonso-t430","subject":"Re: Credential Store: Don't acquire lock when only reading the file","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-30T15:01:36Z","receivedAt":"2020-10-30T15:01:39Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Oct 30, 2020 at 12:05:40PM +0000, Simão Afonso wrote:\n\n> On 2020-10-30 01:55:41, Jeff King wrote:\n> >       - teach it to try the lock until hitting a timeout. I think just\n> > \tswapping out hold_lock_file_for_update() for\n> > \thold_lock_file_for_update_timeout() would do it (I probably\n> > \twould have used it back then, but it didn't exist yet).\n> \n> So I tried to patch the credential store with that timeout function and\n> it seems to solve it! Thanks.\n> \n> https://github.com/git/git/blob/v2.29.2/builtin/credential-store.c#L61\n> \n> This is easily reproduced if you do a `fetch --all` in parallel on the\n> same repository (seen on the attached image):\n\nGreat!\n\n> > $ for n in $(seq 100); do git fetch --all; sleep 0.5; done\n> \n> About the timeout, I put it at infinite retries, but should this be\n> configurable? Or should it be a different default?\n> \n> There aren't many timeout configurations. I found\n> \"core.filesRefLockTimeout\" and \"core.packedRefsTimeout\", doesn't sound\n> like something that should be re-used.\n> Should it be something like this with a different name?\n\nYeah, I don't think it makes sense to reuse those. I kind of doubt that\nit is worth making it configurable, as credential-store does not\notherwise read any config at all.\n\nThe defaults for those other two values are 100ms and 1s respectively.\nSo probably just setting a 1s timeout would be plenty, and saves us from\nlooping forever if we see a stale lockfile.\n\n-Peff\n"}]}