idk, i am new to goland but same thing, tls issue. my tests were very flaky. sometimes they were failing sometimes they weren't. i initially thought it was wsl and goland issue. but after it happened 2-3 times. i ran with goroutines test with -count 50 but it didn't work in goland. then i issued in terminal. which i got to see yup my tests are actually flaky.
i know this is not the best way. but i added a timeout of 10sec. also while asserting i am polling to know if i got a connection.
i understand and i can make it way better. but for now it's good enough.
more importantly, it's better not scatter these kind of codes everywhere.
also making things explicit is better always. like now server.Start is Blocking. and in my tests i am spawning a goroutine, rather than it doing implicitly.
another thing, testing dependencies. i am going to test concurrent handler. and it's not dependent on tls or tcp. i can just use a fake and test the concurrency.
i like this design. scattering these throughout will make every other test integration tests. increasing time to execute and that is bad.
btw, it would have been better if go lang runs tests in parallel.
Go is missing a few things for this. There is no good predictable event-driven polling library for networking with proper error handling and no GC pressure (no heap allocations), etc. And it has to implement its own syscall wrappers, because Go's syscall wrappers on non-blocking FDs call into the scheduler and even produce garbage on some errors. TLS library needs to be predictable too, produce no garbage and play well with polling.
Doing it in idiomatic way and dealing with all that goroutine per request model, concurrent memory access and unpredictable GC pauses is simply not worth it. It's going to be safer, but not of a decent quality. Better to live with what we have.
For Rust, I imagine, it's going to take even more work.
Yeah... so far my Go stuff has had exceptional reliability, I verified I handled all error known conditions with errcheck (https://github.com/kisielk/errcheck) and just generally took the slow and plodding approach of handling everything explicitly. My app handles hundreds of thousands of concurrent connections, each one often using and/or spawning 4 or more goroutines.
Can you blog about it in the future? Couldn't find good resources and tools about testing in Go. Especially with concurrency this seems to be very important.
This is a consequence of dependency injection being a "bad word" among Gophers. But "You don't need that with Go"TM /s.
It's ironic isn't it, in a language that works heavily with concurrency (Go http server is concurrent), all these global states that can potentially introduce race conditions...
Actually, Golang isn't so great. Try to change something lower level, for example in their socket implementation. Also, it's trying to promise a sane concurrency and all code I've seen use mutex all over the place.
This program finds the problem not because TLA+ is nifty, but because it sweeps out the parameter space, which is exactly what the original Go program should have done in a unit test. What happens when `len(pss)` is just below, at, or just above, or way above `concurrencyProcesses`? That is the type of question that unit tests are supposed to address. Go has a perfectly good deadlock detector.
The real problem cannot be solved by re-modelling in a different high-level language, because the real problem with that code is it gets `pss` from a library call and `concurrencyProcesses` from a global variable, when they should both be function parameters a test case can exercise.
The go race detector (go test -race) is actually really good at finding these kinds of issues, regardless of GOMAXPROCS. I've gotten a lot more value from running my tests with the race detector in a single process than running the tests with multiple processes and hoping to encounter thread-safety issues in a debuggable way.
Isn't Go also more likely to be used for concurrent tasks and therefore more likely to exhibit concurrency bugs? I admit I skimmed the article and may have missed this being dealt with in the methodology...
I've built a pretty complex and high performance go-based microservice architecture (~12 services) as backend with testify and gomock for testing. Pretty happy with those choices, never had any issues. The backend doesn't use HTTP, so no opinion there.
I recently started migrating some of the services to Rust for performance reasons. I would say that Go's biggest strength is perhaps its biggest weakness as well: It "just works". Types are loose (no generics), concurrency is extremely easy. This means I can write working Go code really quickly, but as project complexity grows, code tends to become kind of a mess.
For example, channel-based concurrency can become hard to reason about if you have a complex service. A few times I ended up putting mutexes at various places just to make it work despite knowing that it's not the "right" thing to do. Mutexes then come with their own issues. Once you have a deadlock or race condition, good luck debugging it. There exist multiple packages and tools to detect race conditions and deadlocks, so this seems to be a common problem. I must've spent days or weeks worth of time looking at pprof output. You may say it's my fault as a developer to write sloppy code, and that may be true, but the Go language encourages such code with the decisions it made.
The same goes for types. I never needed generics, but the fact that typing is so loose means you can get away with a lot of sloppy code without being punished for it. This can be great for moving fast, but may come to bite you later on.
I love Go and use it daily but the sentiment that it somehow makes multithreaded programming "easy" is greatly over-stated. Channels are easy to deadlock or leave dangling in goroutines and any usage of more standard synchronization primitives have all the same issues as any other language.
That said, built in race condition checking is a great feature and tests should always be run with it. Unfortunately this library would not gain any benefit from it without tests that actually execute code concurrently.
I mentioned concurrency when I said that using more than one system thread didn't help. I tried varying GOMAXPROCS. It either had no effect or made everything slower. And yes, the defaults for loadtest are useless, they need to run for at least a few seconds.
Your results aren't the same ones I got. Maybe it's different with Go 1.2. I have to go and check.
Golang has excellent net stack, but not perfect. In particular, it appears to suffer from the ever-recurring issue of not paying enough attention to teardown. Everything must return to 0, ideally gracefully, but even the best programmers like to gloss over the details, because it’s so easy to just kill everything.
Tcp is full duplex, but net.Conn (the main std API that everyone is using), doesn’t have a “CloseWrite” method. That bubbles down to all downstream uses, such as tls. Another example would be how deadlines and timeouts are handled, and how they (don’t) interact with context, which is the blessed way to do timeouts for all non-IO purposes..
Still, Go is one of the best languages for network programming imo, because it allows you to check most edge cases (like timeouts) without wasting like 4 threads to do so.
1. some tests, over the wire preferably, would be nice
2. redis.go does not seem to be nessary it just changes signature of redis client constructor without much difference, might as well inline its contents
3. using fmt too much, if you don't need run time variables encoding, can do something more simpler. like writing to w.Write([]byte) directly. fmt uses reflect and runtime type detection, better avoid if not needed.
4. code comments do not follow godoc conventions. they should start from symbol name. did you run go fmt and some basic linters?
This is discouraging considering go was initially designed as a systems programming language. I wonder if there is another way for go to handle blocking syscall such that this use case would become reliable.
When I first started with golang I too was flailing around with deadlocks, but once you have spent a bit of time with it, it becomes obvious when you have messed up.
I think that this is more of a lack of familiarity with the language rather than an intrinsic problem with golang.
> the amount of go concurrency bugs I've dealt with has been outstanding and quite brutal over the last few years
Concurrency is hard. Maybe something like Erlang does it better, but compared the concurrency options in most popular languages, and the bugs that go along with those options, I think I would far rather deal with them in Go.
Perhaps the only reason you find concurrency issues in Go so frequently is because Go is more often used where concurrency is necessary, due to it being more suitable to writing concurrent code compared to many other popular languages? I certainly find more DOM bugs in Javascript than other languages, but I'm not sure that is a reflection of the language, only where the language is most often used.
My experience with golang was pretty short; this was back in the days of `go get` and I just couldn't get used to the dependency management system. I think others have had the same problem: https://bluxte.net/musings/2018/04/10/go-good-bad-ugly
IMHO a language is a combination of different factors: the community, the spec, the libraries, the support, the toolchain, etc. and the concurrency model is only one small part of that. If your concurrency model is CSP, the truer that statement may be, because CSP allows you to write highly stateful code, and because given time all user code trends towards the properties of the language and CSP doesn't really enforce a whole lot.
i know this is not the best way. but i added a timeout of 10sec. also while asserting i am polling to know if i got a connection.
i understand and i can make it way better. but for now it's good enough. more importantly, it's better not scatter these kind of codes everywhere. also making things explicit is better always. like now server.Start is Blocking. and in my tests i am spawning a goroutine, rather than it doing implicitly.
another thing, testing dependencies. i am going to test concurrent handler. and it's not dependent on tls or tcp. i can just use a fake and test the concurrency. i like this design. scattering these throughout will make every other test integration tests. increasing time to execute and that is bad.
btw, it would have been better if go lang runs tests in parallel.
reply