5 ms·
All code is inherently not concurrency-safe unless it says so. The http.Client docs mention concurrent usage is safe, but not modification. The closure compile
by minus7 10mo ago
All code is inherently not concurrency-safe unless it says so. The http.Client docs mention concurrent usage is safe, but not modification.
The closure compiler flag trick looks interesting though, will give this a spin on some projects.
- Mawr 10mo ago> The http.Client docs mention concurrent usage is safe, but not modification. Subtle linguistic distinctions are not what I want to see in my docs, especially if the context is concurrency.
- lenkite 10mo ago> Subtle linguistic distinctions are not what I want to see in my docs, especially if the context is concurrency. Which PL do you use then ? Because even Rust makes "Subtle linguistic distinctions" in a lot of places and also in concurrency.
- ViewTrick1002 10mo ago> Because even Rust makes "Subtle linguistic distinctions" in a lot of places and also in concurrency. Please explain
- lenkite 10mo agoRuntime borrow checking: RefCell<T> and Rc<T>. Can give other examples, but admittedly they need `unsafe` blocks. Anyways, the article author lacks basic reading skills, since he forgot to mention that the Go http doc states that only the http client transport is safe for concurrent modification. There is no "subtlety" about it. It directly says so. Concurrent "use" is not Concurrent "modification" in Go. The Go stdlib doc uses this consistently everywhere.
- aystatic 10mo ago> Runtime borrow checking: RefCell<T> and Rc<T>. Can give other examples, but admittedly they need `unsafe` blocks. Where are the “subtle linguistic distinctions”? These types do two completely different things. And neither are even capable of being used in a multithreaded context due to `!Sync` (and `!Send` for Rc and refguards)
- lenkite 10mo agoI did say "runtime borrow checking" ie using them together. Example: `Rc::new(RefCell::new(value));`. This will panic at runtime. Maybe I should have used the phrase "dynamic borrowing" ? https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=64e17e959a37b8814ab00d4869afa434 https://play.rust-lang.org/?version=stable&mode=debug&editio... You don't need different threads. I said concurrency not multi-threading. Interleaving tasks within the same thread (in an event loop for example) can cause panics.
- aystatic 10mo agoI understand what you meant (but note that allocating an Rc isn’t necessary; &RefCell would work just fine). I just didn’t see the “subtle linguistic distinctions” - and still don’t… maybe you could point them out for me? https://doc.rust-lang.org/stable/std/cell/struct.RefCell.html#method.borrow https://doc.rust-lang.org/stable/std/cell/struct.RefCell.htm... https://doc.rust-lang.org/stable/std/cell/struct.RefCell.html#method.borrow_mut https://doc.rust-lang.org/stable/std/cell/struct.RefCell.htm...
- lenkite 10mo agoYeah, it is a crappy example. Ignore me. I just re-read and the rustdoc has no “subtle linguistic distinctions”.
- deleted 10mo ago[deleted]
- unscaled 10mo ago
- zbentley 10mo agoNot GP but off the top of my head: async cancellation, mutex poisoning, drop+clone+thread interactions, and the entire realm of unsafe (which specific language properties no longer hold in an unsafe block? Is undefined behavior present if there’s a defect in unsafe code, or just incorrect behavior? Both answers are indeed subtle and depend on the specifics of the unsafe block). And auto deref coercion, knowing whether a given piece of code allocates, and “into”/turbofish overload lookup, but those subtleties aren’t really concurrency related. I like Rust fine, but it’s got plenty of subtle distinctions.
- saturn_vk 10mo agoOn the other hand, it should be very obvious for anyone that has experience with concurrency, that changing a field on an object like the author showed can never be safe in a concurrency setting. In any language.
- gf000 10mo agoThis is not true in the general case. E.g. setting a field to true from potentially multiple threads can be a completely meaningful operation e.g. if you only care about if ANY of the threads have finished execution. It depends on the platform though (e.g. in Java it is guaranteed that there is no tearing [1]). [1] In OpenJDK. The JVM spec itself only guarantees it for 32-bit primitives and references, but given that 64-bit CPUs can cheaply/freely write a 64-bit value atomically, that's how it's implemented.
- kiitos 10mo ago> setting a field to true from potentially multiple threads can be a completely meaningful operation e.g. if you only care about if ANY of the threads have finished execution. this only works when the language defines a memory model where bools are guaranteed to have atomic reads and writes so you can't make a claim like "setting a field to true from ... multiple threads ... can be a meaningful operation e.g. if you only care about if ANY of the threads have finished execution" as that claim only holds when the memory model allows it which is not true in general, and definitely not true in go assumptions everywhere!!
- gf000 10mo ago> can never be safe in a concurrency setting. In any language. Then I give an example of a language where it's safe I don't get your point. The negation of all is a single example where it doesn't apply.
- zbentley 10mo agoGP didn’t say “setting a ‘bool’ value to true”, it referred to setting a “field”. Interpreted charitably, this would be done in Go via a type that does support atomic updates, which is totally possible.
- kiitos 10mo agothe distinction between "concurrent use" and "concurrent modification" in go is in no way subtle there is this whole demographic of folks, including the OP author, who seem to believe that they can start writing go programs without reading and understanding the language spec, the memory model, or any core docs, and that if the program compiles and runs that any error is the fault of the language rather than the programmer. this just ain't how it works. you have to understand the thing before you can use the thing. all of the bugs in the code in this blog post are immediately obvious to anyone who has even a basic understanding of the rules of the language. this stuff just isn't interesting.
- reader_1000 10mo agoI agree, any direct / field modification should be assumed to be not-thread safe. OTOH, I think Go made a mistake by exporting http.DefaultClient, because it is a pointer and using it causes several problems including thread safety, and there are libraries that use it. It would have been better if it were http.NewDefaultClient() which creates a new one every time it is called.
- unscaled 10mo agoI think the original sin of Go is that it neither allows marking fields or entire structs as immutable (like Rust does) nor does it encourage the use of builder pattern in its standard library (like modern Java does). If, let's say, http.Client was functionally immutable (with all fields being private), and you'd need to have to set everything using a mutable (but inert) http.ClientBuilder, these bugs would not have been possible. You could still share a default client (or a non-default client) efficiently, without ever having to worry about anyone touching a mutable field.