gcnotifier

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 2, 2022 License: MIT Imports: 1 Imported by: 9

README

gcnotifier

gcnotifier provides a way to receive notifications after every run of the garbage collector (GC). Knowing when GC runs is useful to instruct your code to free additional memory resources that it may be using.

GoDoc GoCover

Why?

Package gcnotifier provides a way to receive notifications after every garbage collection (GC) cycle. This can be useful, in long-running programs, to instruct your code to free additional memory resources that you may be using.

A common use case for this is when you have custom data structures (e.g. buffers, rings, trees, pools, ...): instead of setting a maximum size to your data structure you can leave it unbounded and then drop all (or some) of the allocated-but-unused slots after every GC run (e.g. sync.Pool drops all allocated-but-unused objects in the pool during GC).

To minimize the load on the GC the code that runs after receiving the notification should try to avoid allocations as much as possible, or at the very least make sure that the amount of new memory allocated is significantly smaller than the amount of memory that has been "freed" by your code.

GCNotifier guarantees to send a notification after every GC cycle completes. Note that the Go runtime does not guarantee that the GC will run: specifically there is no guarantee that a GC will run before the program terminates.

How to use it

The simplest use of this library is as follows:

gcn := gcnotifier.New()
for range gcn.AfterGC() {
  // this code will be executed after every GC cycle
}
runtime.KeepAlive(gcn) // or store gcn somewhere to keep it alive

As written, the loop above will never terminate, so it is mostly useful if you have global caches that persist for the whole duration of the process.

Note that the AfterGC() call is not guarantee to keep gcn alive. gcn and any channel returned by AfterGC() will automatically get closed if gcn is garbage collected. So if gcn is not kept alive, the for loop will terminate unexpectedly.

If you want to ensure the loop above terminates (e.g. because you only need the notifications for the lifetime of a different object) you can call the Close() method:

gcn := gcnotifier.New()
go func() {
  for range gcn.AfterGC() {
    // this code will be executed after every GC cycle
    // until Close() is called
  }
}()

// later, or elsewhere in your code
gcn.Close() // the loop above will terminate some time after this call

Note that if a loop iteration takes longer than the interval between two GC cycles it is possible that one notification will be dropped. Followup notifications will be still received correctly.

For a more complex example of how to use it have a look at Example() in gcnotifier_test.go.

For details have a look at the documentation.

How it works

gcnotifier uses finalizers to know when a GC run has completed.

Finalizers are run when the garbage collector finds an unreachable block with an associated finalizer.

The SetFinalizer documentation notes that there is no guarantee that finalizers will run before a program exits. This doesn't mean, as sometimes incorrectly understood, that finalizers are not guaranteed to run at all, it just means that they are not guaranteed to run because GC itself is not guaranteed to run in certain situations: e.g. when the runtime is shutting down. Finalizers can also not run for other reasons (e.g. zero-sized or package-level objects) but they don't apply to gcnotifier because care was taken in the implementation to avoid them.

The only other case in which a notification will not be sent by gcnotifier is if your code hasn't consumed a previously-sent notification. In all other cases if a GC cycle completes your code will receive a notification.

The test in gcnotifier_test.go generates garbage in a loop and makes sure that we receive exactly one notification for each of the first 500 GC runs. In my testing I haven't found a way yet to make gcnotifier fail to notify of a GC run short of shutting down the process or failing to receive the notification. If you manage to make it fail in any other way please file a GitHub issue.

License

MIT

Author

Carlo Alberto Ferraris (@cafxx)

Documentation

Overview

Package gcnotifier provides a way to receive notifications after every garbage collection (GC) cycle. This can be useful, in long-running programs, to instruct your code to free additional memory resources that you may be using.

A common use case for this is when you have custom data structures (e.g. buffers, caches, rings, trees, pools, ...): instead of setting a maximum size to your data structure you can leave it unbounded and then drop all (or some) of the allocated-but-unused slots after every GC run (e.g. sync.Pool drops all allocated-but-unused objects in the pool during GC).

To minimize the load on the GC the code that runs after receiving the notification should try to avoid allocations as much as possible, or at the very least make sure that the amount of new memory allocated is significantly smaller than the amount of memory that has been "freed" in response to the notification.

GCNotifier guarantees to send a notification after every GC cycle completes. Note that the Go runtime does not guarantee that the GC will run: specifically there is no guarantee that a GC will run before the program terminates.

Example

Example implements a simple time-based buffering io.Writer: data sent over dataCh is buffered for up to 100ms, then flushed out in a single call to out.Write and the buffer is reused. If GC runs, the buffer is flushed and then discarded so that it can be collected during the next GC run. The example is necessarily simplistic, a real implementation would be more refined (e.g. on GC flush or resize the buffer based on a threshold, perform asynchronous flushes, properly signal completions and propagate errors, adaptively preallocate the buffer based on the previous capacity, etc.)

dataCh := make(chan []byte)
doneCh := make(chan struct{})

out := ioutil.Discard

go func() {
	var buf []byte
	var tick <-chan time.Time
	gcn := New()

	for {
		select {
		case data := <-dataCh:
			if tick == nil {
				tick = time.After(100 * time.Millisecond)
			}
			// received data to write to the buffer
			buf = append(buf, data...)
		case <-tick:
			// time to flush the buffer (but reuse it for the next writes)
			if len(buf) > 0 {
				out.Write(buf)
				buf = buf[:0]
			}
			tick = nil
		case <-gcn.AfterGC():
			// GC just ran: flush and then drop the buffer
			if len(buf) > 0 {
				out.Write(buf)
			}
			buf = nil
			tick = nil
		case <-doneCh:
			// close the writer: flush the buffer and return
			if len(buf) > 0 {
				out.Write(buf)
			}
			return
		}
	}
}()

for i := 0; i < 1<<20; i++ {
	dataCh <- make([]byte, 1024)
}
doneCh <- struct{}{}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type GCNotifier

type GCNotifier struct {
	// contains filtered or unexported fields
}

GCNotifier allows your code to control and receive notifications every time the garbage collector runs.

func New

func New() *GCNotifier

New creates and arms a new GCNotifier.

func (*GCNotifier) AfterGC

func (n *GCNotifier) AfterGC() <-chan struct{}

AfterGC returns the channel that will receive a notification after every GC run. No further notifications will be sent until the previous notification has been consumed. To stop notifications immediately call the Close() method. Otherwise notifications will continue until the GCNotifier object itself is garbage collected. Note that the channel returned by AfterGC will be closed only when GCNotifier is garbage collected. The channel is unique to a single GCNotifier object: use dedicated GCNotifiers if you need to listen for GC notifications in multiple receivers at the same time.

func (*GCNotifier) Close

func (n *GCNotifier) Close()

Close will stop and release all resources associated with the GCNotifier. It is not required to call Close explicitly: when the GCNotifier object is garbage collected Close is called implicitly. If you don't call Close explicitly make sure not to accidently maintain the GCNotifier object alive.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL