Skip to content

Commit

Permalink
fix(map)!: fix map batch methods
Browse files Browse the repository at this point in the history
This breaks the API, returning a count of the number of elements
processed for each batch method.

It also fixes the check against ENOENT and E2BIG.

The selftest has been updated to reflect the new API.
  • Loading branch information
geyslan committed Oct 26, 2023
1 parent eea4a28 commit 9382e51
Show file tree
Hide file tree
Showing 4 changed files with 272 additions and 143 deletions.
69 changes: 39 additions & 30 deletions map-low.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,10 +318,12 @@ func (m *BPFMapLow) DeleteKey(key unsafe.Pointer) error {
// BPFMapLow Batch Operations
//

func (m *BPFMapLow) GetValueBatch(keys unsafe.Pointer, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, error) {
// GetValueBatch gets the values with the given keys from the map.
// It returns the values and the number of read elements.
func (m *BPFMapLow) GetValueBatch(keys, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, uint32, error) {
valueSize, err := calcMapValueSize(m.ValueSize(), m.Type())
if err != nil {
return nil, fmt.Errorf("map %s %w", m.Name(), err)
return nil, 0, fmt.Errorf("map %s %w", m.Name(), err)
}

var (
Expand All @@ -332,7 +334,7 @@ func (m *BPFMapLow) GetValueBatch(keys unsafe.Pointer, startKey, nextKey unsafe.

optsC, errno := C.cgo_bpf_map_batch_opts_new(C.BPF_ANY, C.BPF_ANY)
if optsC == nil {
return nil, fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
return nil, 0, fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
}
defer C.cgo_bpf_map_batch_opts_free(optsC)

Expand All @@ -353,19 +355,22 @@ func (m *BPFMapLow) GetValueBatch(keys unsafe.Pointer, startKey, nextKey unsafe.
optsC,
)
errno = syscall.Errno(-retC)
if retC < 0 && errno != syscall.ENOENT {
return nil, fmt.Errorf("failed to batch get value %v in map %s: %w", keys, m.Name(), errno)
// retC < 0 && errno == syscall.ENOENT indicates a partial read.
if retC < 0 && (errno != syscall.ENOENT || countC == 0) {
return nil, 0, fmt.Errorf("failed to batch get value %v in map %s: %w", keys, m.Name(), errno)
}

// Either some or all entries were read.
// retC < 0 && errno == syscall.ENOENT indicates a partial read.
return collectBatchValues(values, uint32(countC), valueSize), nil
// Either some or all elements were read.
return collectBatchValues(values, uint32(countC), valueSize), uint32(countC), nil
}

func (m *BPFMapLow) GetValueAndDeleteBatch(keys, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, error) {
// GetValueAndDeleteBatch gets the values with the given keys from the map and
// deletes them.
// It returns the values and the number of deleted elements.
func (m *BPFMapLow) GetValueAndDeleteBatch(keys, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, uint32, error) {
valueSize, err := calcMapValueSize(m.ValueSize(), m.Type())
if err != nil {
return nil, fmt.Errorf("map %s %w", m.Name(), err)
return nil, 0, fmt.Errorf("map %s %w", m.Name(), err)
}

var (
Expand All @@ -376,7 +381,7 @@ func (m *BPFMapLow) GetValueAndDeleteBatch(keys, startKey, nextKey unsafe.Pointe

optsC, errno := C.cgo_bpf_map_batch_opts_new(C.BPF_ANY, C.BPF_ANY)
if optsC == nil {
return nil, fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
return nil, 0, fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
}
defer C.cgo_bpf_map_batch_opts_free(optsC)

Expand All @@ -390,21 +395,23 @@ func (m *BPFMapLow) GetValueAndDeleteBatch(keys, startKey, nextKey unsafe.Pointe
optsC,
)
errno = syscall.Errno(-retC)
if retC < 0 && errno != syscall.ENOENT {
return nil, fmt.Errorf("failed to batch lookup and delete values %v in map %s: %w", keys, m.Name(), errno)
// retC < 0 && errno == syscall.ENOENT indicates a partial read and delete.
if retC < 0 && (errno != syscall.ENOENT || countC == 0) {
return nil, 0, fmt.Errorf("failed to batch lookup and delete values %v in map %s: %w", keys, m.Name(), errno)
}

// Either some or all entries were read and deleted.
// retC < 0 && errno == syscall.ENOENT indicates a partial read and delete.
return collectBatchValues(values, uint32(countC), valueSize), nil
// Either some or all elements were read and deleted.
return collectBatchValues(values, uint32(countC), valueSize), uint32(countC), nil
}

func (m *BPFMapLow) UpdateBatch(keys, values unsafe.Pointer, count uint32) error {
// UpdateBatch updates the elements with the given keys and values in the map.
// It returns the number of updated elements.
func (m *BPFMapLow) UpdateBatch(keys, values unsafe.Pointer, count uint32) (uint32, error) {
countC := C.uint(count)

optsC, errno := C.cgo_bpf_map_batch_opts_new(C.BPF_ANY, C.BPF_ANY)
if optsC == nil {
return fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
return 0, fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
}
defer C.cgo_bpf_map_batch_opts_free(optsC)

Expand All @@ -416,22 +423,23 @@ func (m *BPFMapLow) UpdateBatch(keys, values unsafe.Pointer, count uint32) error
optsC,
)
errno = syscall.Errno(-retC)
if retC < 0 {
if errno != syscall.EFAULT && uint32(countC) != count {
return fmt.Errorf("failed to update ALL elements in map %s, updated (%d/%d): %w", m.Name(), uint32(countC), count, errno)
}
return fmt.Errorf("failed to batch update elements in map %s: %w", m.Name(), errno)
// retC < 0 && errno == syscall.E2BIG indicates a partial update.
if retC < 0 && (errno != syscall.E2BIG || countC == 0) {
return 0, fmt.Errorf("failed to batch update values %v in map %s: %w", keys, m.Name(), errno)
}

return nil
// Either some or all elements were updated.
return uint32(countC), nil
}

func (m *BPFMapLow) DeleteKeyBatch(keys unsafe.Pointer, count uint32) error {
// DeleteKeyBatch deletes the elements with the given keys from the map.
// It returns the number of deleted elements.
func (m *BPFMapLow) DeleteKeyBatch(keys unsafe.Pointer, count uint32) (uint32, error) {
countC := C.uint(count)

optsC, errno := C.cgo_bpf_map_batch_opts_new(C.BPF_ANY, C.BPF_ANY)
if optsC == nil {
return fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
return 0, fmt.Errorf("failed to create bpf_map_batch_opts: %w", errno)
}
defer C.cgo_bpf_map_batch_opts_free(optsC)

Expand All @@ -442,12 +450,13 @@ func (m *BPFMapLow) DeleteKeyBatch(keys unsafe.Pointer, count uint32) error {
optsC,
)
errno = syscall.Errno(-retC)
if retC < 0 && errno != syscall.ENOENT {
return fmt.Errorf("failed to batch delete keys %v in map %s: %w", keys, m.Name(), errno)
// retC < 0 && errno == syscall.ENOENT indicates a partial deletion.
if retC < 0 && (errno != syscall.ENOENT || countC == 0) {
return 0, fmt.Errorf("failed to batch delete keys %v in map %s: %w", keys, m.Name(), errno)
}

// retC < 0 && errno == syscall.ENOENT indicates a partial deletion.
return nil
// Either some or all elements were deleted.
return uint32(countC), nil
}

func collectBatchValues(values []byte, count uint32, valueSize int) [][]byte {
Expand Down
69 changes: 42 additions & 27 deletions map.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,6 @@ func (m *BPFMap) DeleteKey(key unsafe.Pointer) error {
//
// The first argument, keys, is a pointer to an array or slice of keys which will
// be populated with the keys returned from this operation.
// It returns the associated values as a slice of slices of bytes.
//
// This API allows for batch lookups of multiple keys, potentially in steps over
// multiple iterations. For example, you provide the last key seen (or nil) for
Expand All @@ -501,14 +500,16 @@ func (m *BPFMap) DeleteKey(key unsafe.Pointer) error {
// previous iteration as the startKey for the next iteration and repeat until
// nextKey is nil.
//
// The last argument, count, is the number of keys to lookup. The kernel will
// update it with the count of the elements that were retrieved.
// The last argument, count, is the number of keys to lookup.
//
// The API can return partial results even though an -1 is returned. In this case,
// errno will be set to `ENOENT` and the values slice and count will be filled in
// with the elements that were read. See the comment in `BPFMapLow.GetValueBatch`
// for more context.
func (m *BPFMap) GetValueBatch(keys unsafe.Pointer, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, error) {
// It returns the associated values as a slice of slices of bytes and the number
// of elements that were retrieved.
//
// The API can return partial results even though the underlying logic received -1.
// In this case, no error will be returned. For checking if the returned values
// are partial, you can compare the number of elements returned with the passed
// count. See the comment in `BPFMapLow.GetValueBatch` for more context.
func (m *BPFMap) GetValueBatch(keys, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, uint32, error) {
return m.bpfMapLow.GetValueBatch(keys, startKey, nextKey, count)
}

Expand All @@ -517,7 +518,6 @@ func (m *BPFMap) GetValueBatch(keys unsafe.Pointer, startKey, nextKey unsafe.Poi
//
// The first argument, keys, is a pointer to an array or slice of keys which will
// be populated with the keys returned from this operation.
// It returns the associated values as a slice of slices of bytes.
//
// This API allows for batch lookups and deletion of multiple keys, potentially
// in steps over multiple iterations. For example, you provide the last key seen
Expand All @@ -527,14 +527,16 @@ func (m *BPFMap) GetValueBatch(keys unsafe.Pointer, startKey, nextKey unsafe.Poi
// previous iteration as the startKey for the next iteration and repeat until
// nextKey is nil.
//
// The last argument, count, is the number of keys to lookup and delete. The kernel
// will update it with the count of the elements that were retrieved and deleted.
// The last argument, count, is the number of keys to lookup and delete.
//
// It returns the associated values as a slice of slices of bytes and the number
// of elements that were retrieved and deleted.
//
// The API can return partial results even though an -1 is returned. In this case,
// errno will be set to `ENOENT` and the values slice and count will be filled in
// with the elements that were read. See the comment in `BPFMapLow.GetValueBatch`
// for more context.
func (m *BPFMap) GetValueAndDeleteBatch(keys, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, error) {
// The API can return partial results even though the underlying logic received -1.
// In this case, no error will be returned. For checking if the returned values
// are partial, you can compare the number of elements returned with the passed
// count. See the comment in `BPFMapLow.GetValueBatch` for more context.
func (m *BPFMap) GetValueAndDeleteBatch(keys, startKey, nextKey unsafe.Pointer, count uint32) ([][]byte, uint32, error) {
return m.bpfMapLow.GetValueAndDeleteBatch(keys, startKey, nextKey, count)
}

Expand All @@ -543,22 +545,35 @@ func (m *BPFMap) GetValueAndDeleteBatch(keys, startKey, nextKey unsafe.Pointer,
//
// The first argument, keys, is a pointer to an array or slice of keys which will
// be updated using the second argument, values.
// It returns the associated error if any occurred.
//
// The last argument, count, is the number of keys to update. Passing an argument
// that greater than the number of keys in the map will cause the function to
// return a syscall.EPERM as an error.
func (m *BPFMap) UpdateBatch(keys, values unsafe.Pointer, count uint32) error {
// The last argument, count, is the number of keys to update.
//
// It returns the number of elements that were updated.
//
// The API can update fewer elements than requested even though the underlying
// logic received -1. This can happen if the map is full and the update operation
// fails for some of the keys. In this case, no error will be returned. For
// checking if the updated values are partial, you can compare the number of
// elements returned with the passed count. See the comment in
// `BPFMapLow.GetValueBatch` and `BPFMapLow.UpdateBatch` for more context.
func (m *BPFMap) UpdateBatch(keys, values unsafe.Pointer, count uint32) (uint32, error) {
return m.bpfMapLow.UpdateBatch(keys, values, count)
}

// DeleteKeyBatch allows for batch deletion of multiple elements in the map.
// DeleteKeyBatch deletes multiple elements from the map by specified keys.
//
// The first argument, keys, is a pointer to an array or slice of keys which will
// be deleted.
//
// The last argument, count, is the number of keys to delete.
//
// It returns the number of elements that were deleted.
//
// `count` number of keys will be deleted from the map. Passing an argument that
// greater than the number of keys in the map will cause the function to delete
// fewer keys than requested. See the comment in `BPFMapLow.GetValueBatch`
// for more context.
func (m *BPFMap) DeleteKeyBatch(keys unsafe.Pointer, count uint32) error {
// The API can delete fewer elements than requested even though the underlying
// logic received -1. For checking if the deleted elements are partial, you can
// compare the number of elements returned with the passed count. See the comment
// in `BPFMapLow.GetValueBatch` for more context.
func (m *BPFMap) DeleteKeyBatch(keys unsafe.Pointer, count uint32) (uint32, error) {
return m.bpfMapLow.DeleteKeyBatch(keys, count)
}

Expand Down
2 changes: 1 addition & 1 deletion selftest/map-batch/main.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, u32);
__type(value, u32);
__uint(max_entries, 1 << 24);
__uint(max_entries, 4);
} tester SEC(".maps");

char LICENSE[] SEC("license") = "Dual BSD/GPL";

0 comments on commit 9382e51

Please sign in to comment.