Use fallocate() rather than truncate() when creating SHM memory

With truncate() the OS might not actually allocate the space leading to
a SIGBUS if /dev/shm runs out of space when actually using the mmap.

By using fallocate we ensure that once the SHM mmap is created it wont
fail
This commit is contained in:
Kovid Goyal
2023-09-23 09:53:17 +05:30
parent 911c80aa3b
commit 2e4f3dab41
6 changed files with 94 additions and 11 deletions

View File

@@ -0,0 +1,29 @@
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
package shm
import (
"fmt"
"syscall"
"unsafe"
)
var _ = fmt.Print
func Fallocate_simple(fd int, size int64) (err error) {
store := &syscall.Fstore_t{
Flags: syscall.F_ALLOCATEALL,
Posmode: syscall.F_PEOFPOSMODE,
Offset: 0,
Length: int64(size),
}
for {
if _, _, err = syscall.Syscall(syscall.SYS_FCNTL, uintptr(out.f.Fd()), syscall.F_PREALLOCATE, uintptr(unsafe.Pointer(store))); !errors.Is(err, unix.EINTR) {
if err != 0 {
return err
}
return nil
}
}
}

View File

@@ -0,0 +1,20 @@
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
package shm
import (
"errors"
"fmt"
"golang.org/x/sys/unix"
)
var _ = fmt.Print
func Fallocate_simple(fd int, size int64) (err error) {
for {
if err = unix.Fallocate(fd, 0, 0, size); !errors.Is(err, unix.EINTR) {
return
}
}
}

View File

@@ -0,0 +1,16 @@
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
//go:build !darwin && !linux
package shm
import (
"fmt"
"golang.org/x/sys/unix"
)
var _ = fmt.Print
func Fallocate_simple(fd int, size int64) (err error) {
return unix.ENOSYS
}

View File

@@ -92,11 +92,19 @@ func CreateTemp(pattern string, size uint64) (MMap, error) {
return create_temp(pattern, size)
}
var force_use_of_fallocate bool = false
func truncate_or_unlink(ans *os.File, size uint64) (err error) {
for {
err = unix.Ftruncate(int(ans.Fd()), int64(size))
if !errors.Is(err, unix.EINTR) {
break
fd := int(ans.Fd())
if err = Fallocate_simple(fd, int64(size)); err != nil {
if force_use_of_fallocate {
return err
}
for {
err = unix.Ftruncate(fd, int64(size))
if !errors.Is(err, unix.EINTR) {
break
}
}
}
if err != nil {
@@ -152,7 +160,7 @@ func ReadWithSizeAndUnlink(name string, file_callback ...func(fs.FileInfo) error
}
defer func() {
mmap.Close()
mmap.Unlink()
_ = mmap.Unlink()
}()
slice, err := ReadWithSize(mmap, 0)
if err != nil {
@@ -164,7 +172,10 @@ func ReadWithSizeAndUnlink(name string, file_callback ...func(fs.FileInfo) error
}
func Read(self MMap, b []byte) (n int, err error) {
pos, _ := self.Seek(0, io.SeekCurrent)
pos, err := self.Seek(0, io.SeekCurrent)
if err != nil {
return 0, err
}
if pos < 0 {
pos = 0
}
@@ -174,7 +185,7 @@ func Read(self MMap, b []byte) (n int, err error) {
return 0, io.EOF
}
n = copy(b, s[pos:])
self.Seek(int64(n), io.SeekCurrent)
_, err = self.Seek(int64(n), io.SeekCurrent)
return
}
@@ -191,7 +202,9 @@ func Write(self MMap, b []byte) (n int, err error) {
return 0, io.ErrShortWrite
}
n = copy(s[pos:], b)
self.Seek(int64(n), io.SeekCurrent)
if _, err = self.Seek(int64(n), io.SeekCurrent); err != nil {
return n, err
}
if n < len(b) {
return n, io.ErrShortWrite
}
@@ -220,7 +233,9 @@ func test_integration_with_python(args []string) (rc int, err error) {
if err != nil {
return 1, err
}
WriteWithSize(mmap, data, 0)
if err = WriteWithSize(mmap, data, 0); err != nil {
return 1, err
}
mmap.Close()
fmt.Println(mmap.Name())
}

View File

@@ -9,14 +9,17 @@ import (
"io/fs"
"os"
"reflect"
"runtime"
"testing"
)
var _ = fmt.Print
func TestSHM(t *testing.T) {
force_use_of_fallocate = runtime.GOOS == "darwin" || runtime.GOOS == "linux"
defer func() { force_use_of_fallocate = false }()
data := make([]byte, 13347)
rand.Read(data)
_, _ = rand.Read(data)
mm, err := CreateTemp("test-kitty-shm-", uint64(len(data)))
if err != nil {
t.Fatal(err)