Skip to content

Commit

Permalink
internal/mmap: add a test for atomic operations on mmapped files
Browse files Browse the repository at this point in the history
While investigating golang/go#68311, we became concerned that
assumptions of cross process atomic operations may not hold on all
platforms.

While this logic should be exercised indirectly via telemetry
integration tests, add an explicit test to the mmap package for this
core behavior.

Updates golang/go#68311

Change-Id: Ibf5049e8a2fe03335e394af17f611207ca0f2016
Reviewed-on: https://go-review.googlesource.com/c/telemetry/+/597336
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Robert Findley <rfindley@google.com>
findleyr authored and gopherbot committed Jul 9, 2024
1 parent af73eac commit 1fde010
Showing 1 changed file with 103 additions and 0 deletions.
103 changes: 103 additions & 0 deletions internal/mmap/mmap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mmap_test

import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"sync"
"sync/atomic"
"testing"
"unsafe"

"golang.org/x/telemetry/internal/mmap"
"golang.org/x/telemetry/internal/testenv"
)

// If the sharedFileEnv environment variable is set,
// increment an atomic value in that file rather than
// run the test.
const sharedFileEnv = "MMAP_TEST_SHARED_FILE"

func TestMain(m *testing.M) {
if name := os.Getenv(sharedFileEnv); name != "" {
_, mapping, err := openMapped(name)
if err != nil {
log.Fatalf("openMapped failed: %v", err)
}

v := (*atomic.Uint64)(unsafe.Pointer(&mapping.Data[0]))
v.Add(1)
// Exit without explicitly calling munmap/close.
os.Exit(0)
}
os.Exit(m.Run())
}

func openMapped(name string) (*os.File, mmap.Data, error) {
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return nil, mmap.Data{}, fmt.Errorf("open failed: %v", err)
}
data, err := mmap.Mmap(f, nil)
if err != nil {
return nil, mmap.Data{}, fmt.Errorf("Mmap failed: %v", err)
}
return f, data, nil
}

func TestSharedMemory(t *testing.T) {
testenv.SkipIfUnsupportedPlatform(t)

// This test verifies that Mmap'ed files are usable for concurrent
// cross-process atomic operations.

dir := t.TempDir()
name := filepath.Join(dir, "shared.count")

var zero [8]byte
if err := os.WriteFile(name, zero[:], 0666); err != nil {
t.Fatal(err)
}

// Fork+exec the current test process.
// Child processes atomically increment the counter file in shared memory.

exe, err := os.Executable()
if err != nil {
t.Fatal(err)
}

const concurrency = 100
var wg sync.WaitGroup
env := append(os.Environ(), sharedFileEnv+"="+name)
for i := 0; i < concurrency; i++ {
i := i
wg.Add(1)
go func() {
defer wg.Done()
cmd := exec.Command(exe)
cmd.Env = env

if err := cmd.Run(); err != nil {
t.Errorf("subcommand #%d failed: %v", i, err)
}
}()
}

wg.Wait()

data, err := os.ReadFile(name)
if err != nil {
t.Fatalf("final read failed: %v", err)
}
v := (*atomic.Uint64)(unsafe.Pointer(&data[0]))
if got := v.Load(); got != concurrency {
t.Errorf("incremented %d times, want %d", got, concurrency)
}
}

0 comments on commit 1fde010

Please sign in to comment.