triplebuffer

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Feb 6, 2024 License: MIT Imports: 1 Imported by: 0

README

triplebuffer

Go Reference

go get github.com/jalavosus/triplebuffer

An attempt at writing a triple buffer system in Go, mainly just to see if I could do it, make it work, AND test it.

There are GoDocs for all public things, a fairly extensive set of tests, and a very basic usage example in example_test.go.

Don't use this in production without personally vetting it. Please. I beg you.

Inspired by the code in this here article, all glory to brilliantsugar.

Documentation

Overview

Package triplebuffer exposes a potentially viable triple-buffer system for managing data between a producer and consumer where avoiding read/write deadlocks might be problematic. Don't trust anything in this package for Production (prod) usage, oh my god.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Buffer

type Buffer[T any] struct {
	// contains filtered or unexported fields
}

Buffer is a triple buffer! Enjoy!

A Buffer has three (private) fields, to wit:

  • back: the Back buffer, which is whatever a producer is working on. This is a pointer, so it is modifiable after setting without any extra work. Initially set by Write, then promoted via atomic swap to the Middle buffer by calling Commit after the producer has finished whatever it needs to do with the data.
  • middle: the Middle buffer, also known as the very special atomic buffer. Data in the Middle buffer can be in one of two states: "pending", or "stale". Pending data is that which has been committed (via Commit), and is considered new data. When Read is next called, the new data will be promoted to the Front buffer, with the previous Front buffer data being atomically swapped into the Middle buffer and marked as "stale". Stale data has been consumed already.
  • front: the Front buffer. Whatever is in here will be returned by Read if there is no pending data in the Middle buffer. If there is pending data in Middle, that data gets promoted and returned by Read.

func NewBuffer

func NewBuffer[T any]() *Buffer[T]

NewBuffer creates a new empty Buffer.

Example

Example of instantiating an empty buffer and writing to it every 750 milliseconds. Thanks to the timer in produce(), reads will rarely be brand new pending reads, but sometimes they can be if the stars align.

package main

import (
	"fmt"
	"time"

	"github.com/jalavosus/triplebuffer"
)

func main() {
	const maxProduceInt int = 42

	produce := func(buf *triplebuffer.Buffer[int]) {
		ticker := time.NewTicker(750 * time.Millisecond)

		for i := 0; i <= maxProduceInt; i++ {
			buf.Write(&i)
			buf.Commit()
			<-ticker.C
		}
	}

	buf := triplebuffer.NewBuffer[int]()
	go produce(buf)

	var prevRead int

	for {
		val, pending := buf.Read()
		if val != nil && *val != prevRead {
			dVal := *val
			fmt.Printf("val: %d - pending %t\n", dVal, pending)

			if dVal >= maxProduceInt {
				break
			}

			prevRead = dVal
		}
	}
}

func NewBufferWithFront

func NewBufferWithFront[T any](front *T) *Buffer[T]

NewBufferWithFront returns a Buffer with the Front buffer populated, and ready to be consumed via Buffer.Read.

func NewPopulatedBuffer

func NewPopulatedBuffer[T any](back, middle, front *T) *Buffer[T]

NewPopulatedBuffer returns a fully populated Buffer, or as populated as the caller makes it, as nil is a valid value for all three params.

Note: the Middle buffer item (`middle`) will be considered Pending unless it is nil.

func (*Buffer[T]) Commit

func (b *Buffer[T]) Commit()

Commit is part of the producer API.

It is called by the producer when it has finished working on (producing) data which is in the Back buffer, and is ready to be moved to the Middle for consumption.

On commit, the following happens:

  • Back buffer data is promoted to the Middle buffer by atomic swap.
  • Middle buffer data is moved into the Back buffer.

Note that Commit can (and, likely at some point *will*) overwrite whatever is in the Middle buffer, even if that data is still pending.

func (*Buffer[T]) Read

func (b *Buffer[T]) Read() (*T, bool)

Read is part of the consumer API. It returns the value currently in the Front buffer.

If the value currently in the Middle buffer is pending, (it has just been commited by a producer) then the Front and Middle buffers will be swapped and the *new* Front buffer returned. Otherwise, the stale Front buffer value will be returned.

If there is no value in the Middle buffer, no swap occurs. The returned boolean corresponds to whether the value is

  • "stale": false
  • "pending": true.

func (*Buffer[T]) Write

func (b *Buffer[T]) Write(data *T)

Write is part of the producer API,

It is passed a pointer to the data currently being "produced", whatever that may be.

Once the production is finished, then Buffer.Commit must be called to commit the new Back buffer to the Middle buffer.

Jump to

Keyboard shortcuts

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