purego: document callback exhaustion and reuse (#516)
Document that each NewCallback call creates a new callback, even for the same function value. Passing func arguments through RegisterFunc also consumes callbacks on every call and can exhaust the callback table. Recommend creating callbacks once and reusing their pointers. Update the documented minimum callback count on Unix to 1024. Closes #521
- commit
- 6673afeb2d5e9ad7af97ed157ab63c653483e2dc
- author
- Hiroki KUMAZAKI · · 2026-09-12 07:08 UTC
- committer
- GitHub · · 2026-09-12 07:08 UTC
- parent
- 9ba4c40
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
2 files · +11 -1
M syscall_unix.go +6 -1
| @@ -23,8 +23,13 @@ func syscall_syscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |||
| 23 | 23 | // This is useful when interoperating with C code requiring callbacks. The argument is expected to be a | |
| 24 | 24 | // function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size | |
| 25 | 25 | // of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated | |
| 26 | - | // for these callbacks is never released. At least 2000 callbacks can always be created. Although this function | |
| 26 | + | // for these callbacks is never released. At least 1024 callbacks can always be created. Although this function | |
| 27 | 27 | // provides similar functionality to windows.NewCallback it is distinct. | |
| 28 | + | // | |
| 29 | + | // Every call to NewCallback creates a new callback even for the same function value, so passing a Go callback to C | |
| 30 | + | // inside a loop (e.g. a qsort comparator) keeps consuming callbacks and eventually panics once they are exhausted. | |
| 31 | + | // The same happens when a func value is passed to a C function, as [RegisterFunc] creates a new callback for each | |
| 32 | + | // call. Create the callback once with NewCallback and reuse the returned pointer instead. | |
| 28 | 33 | func NewCallback(fn any) uintptr { | |
| 29 | 34 | ty := reflect.TypeOf(fn) | |
| 30 | 35 | if ty == nil || ty.Kind() != reflect.Func { | |
| @@ -23,8 +23,13 @@ func syscall_syscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |||||
| 23 | // This is useful when interoperating with C code requiring callbacks. The argument is expected to be a | 23 | // This is useful when interoperating with C code requiring callbacks. The argument is expected to be a | ||
| 24 | // function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size | 24 | // function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size | ||
| 25 | // of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated | 25 | // of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated | ||
| 26 | - | // for these callbacks is never released. At least 2000 callbacks can always be created. Although this function | 26 | + | // for these callbacks is never released. At least 1024 callbacks can always be created. Although this function |
| 27 | // provides similar functionality to windows.NewCallback it is distinct. | 27 | // provides similar functionality to windows.NewCallback it is distinct. | ||
| 28 | + | // | |||
| 29 | + | // Every call to NewCallback creates a new callback even for the same function value, so passing a Go callback to C | |||
| 30 | + | // inside a loop (e.g. a qsort comparator) keeps consuming callbacks and eventually panics once they are exhausted. | |||
| 31 | + | // The same happens when a func value is passed to a C function, as [RegisterFunc] creates a new callback for each | |||
| 32 | + | // call. Create the callback once with NewCallback and reuse the returned pointer instead. | |||
| 28 | func NewCallback(fn any) uintptr { | 33 | func NewCallback(fn any) uintptr { | ||
| 29 | ty := reflect.TypeOf(fn) | 34 | ty := reflect.TypeOf(fn) | ||
| 30 | if ty == nil || ty.Kind() != reflect.Func { | 35 | if ty == nil || ty.Kind() != reflect.Func { | ||
M syscall_windows.go +5 -0
| @@ -23,6 +23,11 @@ func syscall_syscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |||
| 23 | 23 | // allocated for these callbacks is never released. Between NewCallback and NewCallbackCDecl, at least 1024 | |
| 24 | 24 | // callbacks can always be created. Although this function is similar to the darwin version it may act | |
| 25 | 25 | // differently. | |
| 26 | + | // | |
| 27 | + | // Every call to NewCallback creates a new callback even for the same function value, so passing a Go callback to C | |
| 28 | + | // inside a loop (e.g. a qsort comparator) keeps consuming callbacks and eventually panics once they are exhausted. | |
| 29 | + | // The same happens when a func value is passed to a C function, as [RegisterFunc] creates a new callback for each | |
| 30 | + | // call. Create the callback once with NewCallback and reuse the returned pointer instead. | |
| 26 | 31 | func NewCallback(fn any) uintptr { | |
| 27 | 32 | isCDecl := false | |
| 28 | 33 | ty := reflect.TypeOf(fn) | |
| @@ -23,6 +23,11 @@ func syscall_syscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |||||
| 23 | // allocated for these callbacks is never released. Between NewCallback and NewCallbackCDecl, at least 1024 | 23 | // allocated for these callbacks is never released. Between NewCallback and NewCallbackCDecl, at least 1024 | ||
| 24 | // callbacks can always be created. Although this function is similar to the darwin version it may act | 24 | // callbacks can always be created. Although this function is similar to the darwin version it may act | ||
| 25 | // differently. | 25 | // differently. | ||
| 26 | + | // | |||
| 27 | + | // Every call to NewCallback creates a new callback even for the same function value, so passing a Go callback to C | |||
| 28 | + | // inside a loop (e.g. a qsort comparator) keeps consuming callbacks and eventually panics once they are exhausted. | |||
| 29 | + | // The same happens when a func value is passed to a C function, as [RegisterFunc] creates a new callback for each | |||
| 30 | + | // call. Create the callback once with NewCallback and reuse the returned pointer instead. | |||
| 26 | func NewCallback(fn any) uintptr { | 31 | func NewCallback(fn any) uintptr { | ||
| 27 | isCDecl := false | 32 | isCDecl := false | ||
| 28 | ty := reflect.TypeOf(fn) | 33 | ty := reflect.TypeOf(fn) | ||