-
Notifications
You must be signed in to change notification settings - Fork 100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Go 1.6beta1 - Changes to CGO pointer passing break portaudio #7
Comments
Ian Taylor's design document about the changes suggests a way forward:
I'm not sure if the blocking API also needs tweaking, but if so allocating the buffers in C via malloc rather than in Go itself should be simple enough? |
Hi! I've made a fix for this in my fork of this repository, but can't find any contribution guidelines. Should I just submit a PR or is there another preferred way to contribute? |
Hi Orion90! I can't speak for Gordon regarding contribution guidelines but I've finally gotten around to looking at your patch. Allocating memory for the Stream structure via
|
Hey! Thanks for the feedback!
I'll post a comment here when I've updated it, if someone doesn't beat me to it. |
Still, it's good to know the blocking API works with your change. It suggests that the address of the callback function is the only hurdle. |
Hi, now go1.6 is released, any plan to fix it? I can help to code review or if anyone have working patch I can help clean it up. |
Issue is "panic: runtime error: cgo argument has Go pointer to Go pointer" From golang guidline, since go 1.6, "Go code may not store a Go pointer in C memory." This fix use stream id as custom data passed into c, and uses a map to find the stream by id in the callback. Thus no more Go pointer in C memory.
My pull request fixes this issue #10, I would love some feedback. |
Issue is "panic: runtime error: cgo argument has Go pointer to Go pointer" From golang guidline, since go 1.6, "Go code may not store a Go pointer in C memory." This fix use stream id as custom data passed into c, and uses a map to find the stream by id in the callback. Thus no more Go pointer in C memory.
Issue is "panic: runtime error: cgo argument has Go pointer to Go pointer" From golang guidline, since go 1.6, "Go code may not store a Go pointer in C memory." This fix use stream id as custom data passed into c, and uses a map to find the stream by id in the callback. Thus no more Go pointer in C memory.
Nice start @helinwang! It does appear that you are adhering to Ian Taylor's design document which @sqweek referenced. I refactored the code to make it a bit more composable. I also found a case in Start() where we could add a pointer to the map but an error may never release it. I debated on what to call the functions and am open to suggestions. I didn't figure Release() or Free() would work because we aren't truly operating on memory, we are simply no longer tracking a pointer so I implemented Track() and Untrack(). I am sure there are further issues with my code too. More eyeballs on it would be wonderful. |
Man, my gofmt vim plugin is making that diff bleed way more red than needed. I really didn't change that much. |
I'm going to fix whatever happened to the formatting so the diff is better represented. That is just too much fuglyness to ask people to look through. |
Alright that is a bit better. |
Calling The good news it that the lock is not necessary at this stage. The callback will never be invoked before Also the PR touches too many places in the code. The only functions that need to care about the mapping are |
@sqweek thanks for the comment. I have one question about locking - bear with me if I am wrong, I am not totally confident about understanding of synchronization. I thought lock is necessary in stream callback because map maybe modified from different go routine. E.g, the open/close go routine. If only modified from open go routine, it kind of make sense: open surely will happen before callback, so lock as a memory barrier will make sure modification is propagated to different go routines. But it's not the case for close. Maybe change to RW lock will solve the potential latency issue? --- btw, I think even lock (not RWLock) is pretty fast. |
@jaredfolkins thanks for the comments, yes you are right, many places error case prevent *Stream from removed from the map. One idea, maybe use unsafe.SetFinalizer to do it? |
If you call Pa_CloseStream before removing the pointer from the map then it shouldn't matter in the close case either. If we want to be really cautious a better compromise would be to spawn a go routine in close that sleeps for a few seconds before clearing the map entry. Doesn't matter if it hangs around a bit longer, we just need to ensure it is there while the callback is active. I agree mutex is fast on average, but it's the worst case we need to worry about because that is what will cause glitches in your audio stream. If you try to acquire the lock but someone else has it and the go routine running the callback goes to sleep, when does it get rescheduled? Who guarantees that it gets rescheduled before the deadline? Maybe it does usually wake up in time, I don't know for sure. But I don't feel introducing this kind of uncertainty is acceptable behaviour for an audio library. Also you can start and stop a steam many times in between open and close, so errors on those code paths shouldn't affect the map. -----Original Message----- @sqweek thanks for the comment. I have one question about locking - bear with me if I am wrong, I am not totally confident about understanding of synchronization. |
@sqweek @helinwang nice job talking this out. I'll add some more thoughts and research. What does PortAudio do?PortAudio has a example program that actually uses a ringbuffer to help with performance on a blocking operation. https://subversion.assembla.com/svn/portaudio/portaudio/trunk/pablio/pablio.c What is the rest of the Golang/Cgo community doing?I used some Search-Fu to find examples of what other community members are doing. The results are here which allowed me to track down this commit here. What is hilarious is that this commit is very similar to my initial commit for PortAudio which I wrote without even searching around. Here is another one, using channels, which I think is the wrong approach. One idea, maybe use unsafe.SetFinalizer to do it?I found this post/debate about a proposal for depricating SetFinalizer(). The proposal was withdrawn but it does bring up some interesting stuff. What is everyone's thoughts about it? https://groups.google.com/forum/#!topic/golang-dev/DMiUkpS1uyQ Maybe change to RW lock will solve the potential latency issue? --- btw, I think even lock (not RWLock) is pretty fast.Yes, we should most certainly do this for read operations. https://golang.org/pkg/sync/#RWMutex.RLock The good news it that the lock is not necessary at this stage. The callback will never be invoked before Pa_OpenStream is called, so by the time we get there we know that idToStream has been filled out.Any operation on a map with atomicity requires locking. Reading, Writing, Updating, Deleting. All must lock. https://news.ycombinator.com/item?id=10986069 Also you can start and stop a stream many times in between open and close, so errors on those code paths shouldn't affect the map.I think it would depend on the type of error no? My vote is that we don't fear locking, it is insanely fast. You'll probably run into a bottle neck with millions of operations per second but I don't think we have to worry about that for now. If it does become an issue, we could just use some fancy ring hash to make it performant. Keep posting your thoughts. At least we are all taking the time to figure out the best thing for the library. -jf |
Thanks for the detailed reply @jaredfolkins Yes you are right, the Unlock does not propagate information to all other go routine. It's only guaranteed that next mu.Lock() will happen before mu.Unlock(). So I think lock is necessary this case in this call back. Reference https://golang.org/ref/mem#tmp_8 I my experience SetFinalizer is a good use case for cgo where you allocate a c memory and need to free it when the owner go object goes away (don't know if there is any better solution than this) |
Another thread. Another similar solution. We are on the right track. |
Yes, the point of the ringbuffer is to avoid blocking in the audio callback. I'm not making this issue up, it took me two minutes to find these snippets:
I think its misleading to say the ringbuffer is about "helping with performance". I could accept "helping with worst-case performance", but they both imply this is an optimisation step as opposed to a domain requirement. To be fair these concerns are primarily talking about pthread style mutexes which invoke the OS scheduler. I'm not sure if go's sync.Mutex uses that kind of mutex under the hood or has different characteristics. I'll concede that accessing the map concurrently without locks is not safe - I haven't looked at the map implementation in detail but I guess the consequence if something rebalances the map while the callback is trying to look it up is it might not find the entry for the given key? Which is just as bad as missing the deadline since we can't determine which stream callback to invoke. I don't have any better idea at the moment. Something other than a map might work without locks (eg. a series of fixed-size arrays in a linked list?), but seems like it gets complex quickly. I'm not an expert on the go memory model but I've had good results with a lockless ring buffer implementation backed by a fixed size array.
I don't think so. http://portaudio.com/docs/v19-doxydocs/terminating_portaudio.html says:
ie. the client must call stream.Close to avoid leaking. If they don't it seems perfectly reasonable for us to leak a map entry + Stream reference. There is currently a missing Untrack in OpenStream if Pa_OpenStream fails. You could also get rid of the previous two Untracks:
|
Implementation with @sqweek's suggestions. Please review. |
I still don't like the mutex, but I finally ran a test to see how C calling back into go interacts with the scheduler. The test results suggest that if all Ms are in use when the callback is invoked, the calling thread will block (even when that calling thread was not created by go). ie. the callback approach is already burdened with significant realtime pitfalls. Which is not an argument for adding more, but the mutex is at least easier to manage in the common case where a single Stream is active at once. So I'm not going to insist on a different solution and would be happy to see this patch merged. I left a couple of final comments (nitpicks really) on d559694. |
Changes made, finalized PR sent. |
Hi. but no luck. I'm not familiar with windows compilation (and with "C" compilation globally .. I'm more form Java world) so maybe I missed something obvious. Thanks if you can help me. Mike |
@mbaroukh Yeah pkg-config can be stupidly annoying to get going on windows. Try searching for pkg-config-lite - it has less dependencies and you should be able to just drop the exe into your mingw bin/ dir. If that doesn't work please shoot me an email rather than spamming this thread - sqweek at gmail. |
Thanks for your reply @sqweek ! I'll ty. |
Sorry for my silence, I only noticed this issue today; my notification settings must be screwed up. Anyway, this should be fixed now. |
Hi,
I tried compiling portaudio against the new golang 1.6 beta 1 release and it breaks port audio bindings. As a part of 1.6 they tighten the rules regarding Go pointers being passed into C memory.
This means that while programs compile, at run-time they abort. See below for the noise.go example from the distribution.
This example worked fine on 1.5.x and lower.
The related changes are described in golang/go#12416.
The text was updated successfully, but these errors were encountered: