├── .gitignore ├── sync ├── internal │ └── test │ │ ├── cond │ │ ├── .gitignore │ │ └── main.go │ │ ├── sema │ │ ├── .gitignore │ │ └── main.go │ │ ├── event │ │ ├── .gitignore │ │ └── main.go │ │ └── locker │ │ ├── .gitignore │ │ ├── locker_helper_windows.go │ │ └── locker_helper_unix.go ├── doc.go ├── sys_darwin_amd64.s ├── sys_sema_freebsd.go ├── sys_sema_linux+darwin.go ├── sys_darwin_386.s ├── mutex_ev_windows_test.go ├── thread_darwin.go ├── mutex_helper_windows.go ├── sema_timed_linux.go ├── sync.go ├── mutex_helper_futex.go ├── mutex_helper_unix.go ├── thread_freebsd.go ├── sys_sema_linux_amd64.go ├── mutex_futex_test.go ├── futex.go ├── mutex.go ├── mutex_spin_test.go ├── sema_sys.go ├── mutex_test.go ├── cond_event.go ├── mutex_sysv_test.go ├── cond_spin.go ├── event.go ├── event_futex.go ├── sema_windows.go ├── sys_sema_linux_386.go ├── cond.go ├── futex_freebsd.go ├── event_windows.go ├── sema_timed_bsd.go ├── event_sema.go ├── event_spin.go ├── lwmutex.go ├── lwevent.go ├── semaphore.go ├── futex_linux.go ├── cond_futex.go ├── sync_example_test.go ├── mutex_futex.go ├── rwmutex_test.go ├── mutex_spin.go ├── mutex_sema.go ├── lwrwmutex.go ├── sema_unix.go └── mutex_ev_windows.go ├── shm ├── internal │ └── test │ │ ├── .gitignore │ │ ├── shm_helper_unix.go │ │ ├── shm_helper_windows.go │ │ └── main.go ├── doc.go ├── shared_memory_linux_test.go ├── shared_memory_bsd.go ├── shared_memory_windows.go ├── shared_memory_unix.go ├── shared_memory_example_test.go ├── shared_memory.go ├── shared_memory_native_windows_test.go └── shared_memory_native_windows.go ├── mq ├── .gitignore ├── internal │ └── test │ │ ├── .gitignore │ │ ├── mq_helper_windows.go │ │ ├── mq_helper_unix.go │ │ ├── mq_helper_linux.go │ │ └── main.go ├── doc.go ├── mq_sysv_sys_bsd.go ├── mq_sysv_sys_linux+darwin.go ├── temp_error.go ├── mq_helper_fast.go ├── mq_helper_linux.go ├── mq_helper_sysv.go ├── mq_fast_impl.go ├── mq_sysv_test.go ├── mq_sysv_sys.go ├── mq_sys_sysv_linux_386.go ├── shared_heap.go ├── mq_fast_test.go ├── mq_example_test.go ├── mq.go └── mq_prio_test.go ├── docs ├── futex.pdf ├── _umtx_op.2.pdf └── implementingcvs.pdf ├── mmf ├── testdata │ └── test.bin ├── doc.go ├── reader_writer.go ├── memory_region_unix.go ├── memory_region_windows.go └── mmf_test.go ├── fifo ├── internal │ └── test │ │ ├── .gitignore │ │ └── main.go ├── doc.go ├── fifio_helper_windows.go ├── fifo_helper_unix.go ├── fifo.go ├── fifo_unix.go └── fifo_sys_windows.go ├── go.mod ├── internal ├── allocator │ ├── use.s │ └── object_allocator_test.go ├── common │ ├── common_windows.go │ ├── common.go │ └── common_unix.go ├── test │ └── utils_test.go ├── helper │ └── helper.go ├── array │ ├── mapped_array.go │ └── shared_array_test.go └── sys │ └── windows │ └── syscall_windows.go ├── contributors.txt ├── doc.go ├── NOTICE ├── .circleci └── config.yml ├── go.sum ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.test 2 | *.DS_Store 3 | -------------------------------------------------------------------------------- /sync/internal/test/cond/.gitignore: -------------------------------------------------------------------------------- 1 | /cond 2 | -------------------------------------------------------------------------------- /shm/internal/test/.gitignore: -------------------------------------------------------------------------------- 1 | /test.exe 2 | /test 3 | -------------------------------------------------------------------------------- /mq/.gitignore: -------------------------------------------------------------------------------- 1 | /prof.block 2 | /prof.cpu 3 | /prof.mem 4 | -------------------------------------------------------------------------------- /sync/internal/test/sema/.gitignore: -------------------------------------------------------------------------------- 1 | /sema 2 | *.exe 3 | -------------------------------------------------------------------------------- /sync/internal/test/event/.gitignore: -------------------------------------------------------------------------------- 1 | /event 2 | /event.exe 3 | -------------------------------------------------------------------------------- /mq/internal/test/.gitignore: -------------------------------------------------------------------------------- 1 | /mq 2 | *.directory 3 | /test 4 | /test.exe 5 | -------------------------------------------------------------------------------- /docs/futex.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-board/go-ipc/HEAD/docs/futex.pdf -------------------------------------------------------------------------------- /sync/internal/test/locker/.gitignore: -------------------------------------------------------------------------------- 1 | /sync 2 | /sync.exe 3 | /locker.exe 4 | /locker 5 | -------------------------------------------------------------------------------- /docs/_umtx_op.2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-board/go-ipc/HEAD/docs/_umtx_op.2.pdf -------------------------------------------------------------------------------- /mmf/testdata/test.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-board/go-ipc/HEAD/mmf/testdata/test.bin -------------------------------------------------------------------------------- /fifo/internal/test/.gitignore: -------------------------------------------------------------------------------- 1 | /fifo.exe 2 | /fifo 3 | /test.exe 4 | *.directory 5 | /test.exe 6 | -------------------------------------------------------------------------------- /docs/implementingcvs.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-board/go-ipc/HEAD/docs/implementingcvs.pdf -------------------------------------------------------------------------------- /shm/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // Package shm implements shared memory objects. 4 | package shm 5 | -------------------------------------------------------------------------------- /mmf/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // Package mmf implements privitives for mapping files into memory. 4 | package mmf 5 | -------------------------------------------------------------------------------- /sync/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // Package sync implements primitives for synchronization between processes. 4 | package sync 5 | -------------------------------------------------------------------------------- /sync/sys_darwin_amd64.s: -------------------------------------------------------------------------------- 1 | #include "textflag.h" 2 | 3 | TEXT ·mach_thread_self(SB),NOSPLIT,$0 4 | MOVL $(0x1000000+27), AX // mach_thread_self 5 | SYSCALL 6 | MOVL AX, ret+0(FP) 7 | RET 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module bitbucket.org/avd/go-ipc 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/pkg/errors v0.8.1 7 | github.com/stretchr/testify v1.3.0 8 | golang.org/x/sys v0.0.0-20190507053917-2953c62de483 9 | ) 10 | -------------------------------------------------------------------------------- /fifo/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // Package fifo implements first-in-first-out objects logic. 4 | // It gives access to OS-native FIFO objects via: 5 | // CreateNamedPipe on windows 6 | // Mkfifo on unix 7 | package fifo 8 | -------------------------------------------------------------------------------- /internal/allocator/use.s: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include "textflag.h" 6 | 7 | TEXT ·Use(SB),NOSPLIT,$0 8 | RET 9 | -------------------------------------------------------------------------------- /sync/sys_sema_freebsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | func init() { 6 | // values from http://fxr.watson.org/fxr/source/kern/syscalls.master 7 | sysSemGet = 221 8 | sysSemCtl = 220 9 | sysSemOp = 222 10 | } 11 | -------------------------------------------------------------------------------- /mq/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // Package mq implements interprocess queues logic. 4 | // It provides access to system mq mechanisms, such as sysv mq and linux mq. 5 | // Also, it provides access to multi-platform priority queue, FastMq. 6 | package mq 7 | -------------------------------------------------------------------------------- /mq/mq_sysv_sys_bsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build freebsd 4 | 5 | package mq 6 | 7 | func init() { 8 | // values from http://fxr.watson.org/fxr/source/kern/syscalls.master 9 | sysMsgCtl = 224 10 | sysMsgGet = 225 11 | sysMsgSnd = 226 12 | sysMsgRcv = 227 13 | } 14 | -------------------------------------------------------------------------------- /sync/sys_sema_linux+darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build linux,amd64 darwin 4 | 5 | package sync 6 | 7 | import "golang.org/x/sys/unix" 8 | 9 | func init() { 10 | sysSemGet = unix.SYS_SEMGET 11 | sysSemCtl = unix.SYS_SEMCTL 12 | sysSemOp = unix.SYS_SEMOP 13 | } 14 | -------------------------------------------------------------------------------- /sync/sys_darwin_386.s: -------------------------------------------------------------------------------- 1 | #include "textflag.h" 2 | 3 | TEXT sysenter(SB),NOSPLIT,$0 4 | POPL DX 5 | MOVL SP, CX 6 | BYTE $0x0F; BYTE $0x34; // SYSENTER 7 | // returns to DX with SP set to CX 8 | 9 | TEXT ·mach_thread_self(SB),NOSPLIT,$0 10 | MOVL $-27, AX 11 | CALL sysenter(SB) 12 | MOVL AX, ret+0(FP) 13 | RET 14 | -------------------------------------------------------------------------------- /fifo/fifio_helper_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package fifo 4 | 5 | import "os" 6 | 7 | func newFifo(name string, flag int, perm os.FileMode) (Fifo, error) { 8 | return NewNamedPipe(name, flag, perm) 9 | } 10 | 11 | func destroyFifo(name string) error { 12 | return DestroyNamedPipe(name) 13 | } 14 | -------------------------------------------------------------------------------- /mq/mq_sysv_sys_linux+darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build linux,amd64 darwin 4 | 5 | package mq 6 | 7 | import "golang.org/x/sys/unix" 8 | 9 | func init() { 10 | sysMsgCtl = unix.SYS_MSGCTL 11 | sysMsgGet = unix.SYS_MSGGET 12 | sysMsgRcv = unix.SYS_MSGRCV 13 | sysMsgSnd = unix.SYS_MSGSND 14 | } 15 | -------------------------------------------------------------------------------- /contributors.txt: -------------------------------------------------------------------------------- 1 | # go-ipc Contributor List: 2 | # This is the official list of people who can contribute 3 | # (and typically have contributed) code to the go-ipc repository. 4 | # 5 | # Names should be added to this file like so: 6 | # Individual's name 7 | # 8 | 9 | # Please keep the list sorted. 10 | 11 | Alexander Demakin -------------------------------------------------------------------------------- /fifo/fifo_helper_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build darwin freebsd linux 4 | 5 | package fifo 6 | 7 | import "os" 8 | 9 | func newFifo(name string, flag int, perm os.FileMode) (Fifo, error) { 10 | return NewUnixFifo(name, flag, perm) 11 | } 12 | 13 | func destroyFifo(name string) error { 14 | return DestroyUnixFIFO(name) 15 | } 16 | -------------------------------------------------------------------------------- /sync/mutex_ev_windows_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func BenchmarkEventMutex(b *testing.B) { 11 | benchmarkLocker(b, func(name string, mode int, perm os.FileMode) (IPCLocker, error) { 12 | return NewEventMutex(name, mode, perm) 13 | }, func(name string) error { 14 | return DestroyEventMutex(name) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /sync/thread_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import "golang.org/x/sys/unix" 6 | 7 | func mach_thread_self() uint32 8 | 9 | func gettid() (uint32, error) { 10 | return mach_thread_self(), nil 11 | } 12 | 13 | func killThread(port uint32) error { 14 | _, _, err := unix.Syscall(unix.SYS___PTHREAD_KILL, uintptr(port), uintptr(unix.SIGUSR2), 0) 15 | return err 16 | } 17 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // Package ipc provides primitives for inter-process communication. 4 | // Works on Linux, OSX, FreeBSD, and Windows (x86 or x86-64). 5 | // Supports following mechanisms: 6 | // fifo (unix and windows pipes) 7 | // memory mapped files 8 | // shared memory 9 | // system message queues (Linux, FreeBSD, OSX) 10 | // cross-platform priority message queue 11 | // mutexes, rw mutexes 12 | // semaphores 13 | // events 14 | // conditional variables 15 | package ipc 16 | -------------------------------------------------------------------------------- /mq/temp_error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package mq 4 | 5 | type temporaryError struct { 6 | inner error 7 | } 8 | 9 | func (e *temporaryError) isTemporary() bool { 10 | return true 11 | } 12 | 13 | func (e *temporaryError) Error() string { 14 | return e.inner.Error() 15 | } 16 | 17 | func newTemporaryError(inner error) *temporaryError { 18 | return &temporaryError{inner: inner} 19 | } 20 | 21 | func isTemporaryError(e error) bool { 22 | if tmp, ok := e.(*temporaryError); ok { 23 | return tmp.isTemporary() 24 | } 25 | return false 26 | } 27 | -------------------------------------------------------------------------------- /sync/mutex_helper_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import "os" 6 | 7 | // this is to ensure, that all implementations of ipc mutex satisfy the same minimal interface. 8 | var ( 9 | _ TimedIPCLocker = (*EventMutex)(nil) 10 | ) 11 | 12 | func newMutex(name string, flag int, perm os.FileMode) (TimedIPCLocker, error) { 13 | l, err := NewEventMutex(name, flag, perm) 14 | if err != nil { 15 | return nil, err 16 | } 17 | return l, nil 18 | } 19 | 20 | func destroyMutex(name string) error { 21 | return DestroyEventMutex(name) 22 | } 23 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Aleksandr Demakin 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /sync/sema_timed_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "time" 7 | 8 | "bitbucket.org/avd/go-ipc/internal/common" 9 | ) 10 | 11 | func doSemaTimedWait(id int, timeout time.Duration) bool { 12 | err := common.UninterruptedSyscallTimeout(func(curTimeout time.Duration) error { 13 | b := sembuf{semnum: 0, semop: int16(-1), semflg: 0} 14 | return semtimedop(id, []sembuf{b}, common.TimeoutToTimeSpec(curTimeout)) 15 | }, timeout) 16 | if err == nil { 17 | return true 18 | } 19 | if common.IsTimeoutErr(err) { 20 | return false 21 | } 22 | panic(err) 23 | } 24 | -------------------------------------------------------------------------------- /sync/sync.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "os" 7 | "time" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | // ensureOpenFlags ensures, that no other flags but os.O_CREATE and os.O_EXCL are set. 13 | func ensureOpenFlags(flags int) error { 14 | if flags & ^(os.O_CREATE|os.O_EXCL) != 0 { 15 | return errors.New("only os.O_CREATE and os.O_EXCL are allowed") 16 | } 17 | return nil 18 | } 19 | 20 | // waitWaker is an object, which implements wake/wait semantics. 21 | type waitWaker interface { 22 | wake(count int32) (int, error) 23 | wait(value int32, timeout time.Duration) error 24 | } 25 | -------------------------------------------------------------------------------- /sync/mutex_helper_futex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build linux,!sysv_mutex_linux freebsd,!sysv_mutex_freebsd 4 | 5 | package sync 6 | 7 | import "os" 8 | 9 | // this is to ensure, that all implementations of ipc mutex satisfy the same minimal interface. 10 | var ( 11 | _ TimedIPCLocker = (*FutexMutex)(nil) 12 | ) 13 | 14 | func newMutex(name string, flag int, perm os.FileMode) (TimedIPCLocker, error) { 15 | l, err := NewFutexMutex(name, flag, perm) 16 | if err != nil { 17 | return nil, err 18 | } 19 | return l, nil 20 | } 21 | 22 | func destroyMutex(name string) error { 23 | return DestroyFutexMutex(name) 24 | } 25 | -------------------------------------------------------------------------------- /mq/mq_helper_fast.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | //+build windows 4 | 5 | package mq 6 | 7 | import "os" 8 | 9 | func createMQ(name string, flag int, perm os.FileMode) (Messenger, error) { 10 | mq, err := CreateFastMq(name, flag, perm, DefaultFastMqMaxSize, DefaultFastMqMessageSize) 11 | if err != nil { 12 | return nil, err 13 | } 14 | return mq, nil 15 | } 16 | 17 | func openMQ(name string, flags int) (Messenger, error) { 18 | mq, err := OpenFastMq(name, flags) 19 | if err != nil { 20 | return nil, err 21 | } 22 | return mq, nil 23 | } 24 | 25 | func destroyMq(name string) error { 26 | return DestroyFastMq(name) 27 | } 28 | -------------------------------------------------------------------------------- /sync/mutex_helper_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build darwin linux,sysv_mutex_linux freebsd,sysv_mutex_freebsd 4 | 5 | package sync 6 | 7 | import "os" 8 | 9 | // this is to ensure, that all implementations of ipc mutex 10 | // satisfy the same minimal interface 11 | var ( 12 | _ TimedIPCLocker = (*SemaMutex)(nil) 13 | ) 14 | 15 | func newMutex(name string, flag int, perm os.FileMode) (TimedIPCLocker, error) { 16 | l, err := NewSemaMutex(name, flag, perm) 17 | if err != nil { 18 | return nil, err 19 | } 20 | return l, nil 21 | } 22 | 23 | func destroyMutex(name string) error { 24 | return DestroySemaMutex(name) 25 | } 26 | -------------------------------------------------------------------------------- /sync/thread_freebsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | 9 | "bitbucket.org/avd/go-ipc/internal/allocator" 10 | 11 | "golang.org/x/sys/unix" 12 | ) 13 | 14 | func gettid() (int, error) { 15 | var tid int64 16 | tidPtr := unsafe.Pointer(&tid) 17 | _, _, err := unix.Syscall(unix.SYS_THR_SELF, uintptr(tidPtr), uintptr(0), uintptr(0)) 18 | allocator.Use(tidPtr) 19 | if err != syscall.Errno(0) { 20 | return 0, err 21 | } 22 | return int(tid), nil 23 | } 24 | 25 | func killThread(tid int) error { 26 | _, _, err := unix.Syscall(unix.SYS_THR_KILL, uintptr(tid), uintptr(unix.SIGUSR2), 0) 27 | return err 28 | } 29 | -------------------------------------------------------------------------------- /mq/mq_helper_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | //+build linux_mq 4 | 5 | package mq 6 | 7 | import "os" 8 | 9 | func createMQ(name string, flag int, perm os.FileMode) (Messenger, error) { 10 | mq, err := CreateLinuxMessageQueue(name, flag, perm, DefaultLinuxMqMaxSize, DefaultLinuxMqMessageSize) 11 | if err != nil { 12 | return nil, err 13 | } 14 | return mq, nil 15 | } 16 | 17 | func openMQ(name string, flags int) (Messenger, error) { 18 | mq, err := OpenLinuxMessageQueue(name, flags|os.O_RDWR) 19 | if err != nil { 20 | return nil, err 21 | } 22 | return mq, nil 23 | } 24 | 25 | func destroyMq(name string) error { 26 | return DestroyLinuxMessageQueue(name) 27 | } 28 | -------------------------------------------------------------------------------- /internal/common/common_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package common 4 | 5 | import ( 6 | "os" 7 | "syscall" 8 | ) 9 | 10 | const ( 11 | cERROR_TIMEOUT = syscall.Errno(1460) 12 | ) 13 | 14 | // IsTimeoutErr returns true, if the given error is a temporary syscall error. 15 | func IsTimeoutErr(err error) bool { 16 | if sysErr, ok := err.(*os.SyscallError); ok { 17 | if errno, ok := sysErr.Err.(syscall.Errno); ok { 18 | return errno == cERROR_TIMEOUT 19 | } 20 | } 21 | return false 22 | } 23 | 24 | // NewTimeoutError returns new syscall error with ERROR_TIMEOUT code. 25 | func NewTimeoutError(op string) error { 26 | return os.NewSyscallError(op, cERROR_TIMEOUT) 27 | } 28 | -------------------------------------------------------------------------------- /mq/mq_helper_sysv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build darwin freebsd linux 4 | // +build !linux_mq 5 | // +build !fast_mq 6 | 7 | package mq 8 | 9 | import "os" 10 | 11 | func createMQ(name string, flag int, perm os.FileMode) (Messenger, error) { 12 | mq, err := CreateSystemVMessageQueue(name, flag, perm) 13 | if err != nil { 14 | return nil, err 15 | } 16 | return mq, nil 17 | } 18 | 19 | func openMQ(name string, flag int) (Messenger, error) { 20 | mq, err := OpenSystemVMessageQueue(name, flag) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return mq, nil 25 | } 26 | 27 | func destroyMq(name string) error { 28 | return DestroySystemVMessageQueue(name) 29 | } 30 | -------------------------------------------------------------------------------- /sync/sys_sema_linux_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "os" 7 | "syscall" 8 | "unsafe" 9 | 10 | "bitbucket.org/avd/go-ipc/internal/allocator" 11 | 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | func semtimedop(id int, ops []sembuf, timeout *unix.Timespec) error { 16 | if len(ops) == 0 { 17 | return nil 18 | } 19 | pOps := unsafe.Pointer(&ops[0]) 20 | pTimeout := unsafe.Pointer(timeout) 21 | _, _, err := unix.Syscall6(unix.SYS_SEMTIMEDOP, uintptr(id), uintptr(pOps), uintptr(len(ops)), uintptr(pTimeout), 0, 0) 22 | allocator.Use(pOps) 23 | allocator.Use(pTimeout) 24 | if err != syscall.Errno(0) { 25 | return os.NewSyscallError("SEMTIMEDOP", err) 26 | } 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /shm/internal/test/shm_helper_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build linux darwin freebsd 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | 11 | "bitbucket.org/avd/go-ipc/shm" 12 | ) 13 | 14 | func newShmObject(name string, mode int, perm os.FileMode, typ string, size int) (*shm.MemoryObject, error) { 15 | switch typ { 16 | case "default", "": 17 | return shm.NewMemoryObject(name, mode, perm) 18 | default: 19 | return nil, fmt.Errorf("unknown object type '%s'", typ) 20 | } 21 | } 22 | 23 | func destroyShmObject(name string, typ string) error { 24 | switch typ { 25 | case "default", "": 26 | return shm.DestroyMemoryObject(name) 27 | default: 28 | return fmt.Errorf("unknown object type '%s'", typ) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sync/mutex_futex_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build linux freebsd 4 | 5 | package sync 6 | 7 | import ( 8 | "os" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func BenchmarkFutexMutex(b *testing.B) { 15 | benchmarkLocker(b, func(name string, mode int, perm os.FileMode) (IPCLocker, error) { 16 | return NewFutexMutex(name, mode, perm) 17 | }, func(name string) error { 18 | return DestroyFutexMutex(name) 19 | }) 20 | } 21 | 22 | func BenchmarkFutexMutexAsRW(b *testing.B) { 23 | a := assert.New(b) 24 | DestroyFutexMutex(testLockerName) 25 | m, err := NewFutexMutex(testLockerName, os.O_CREATE|os.O_EXCL, 0666) 26 | if !a.NoError(err) { 27 | return 28 | } 29 | defer m.Close() 30 | benchmarkRWLocker(b, m, m) 31 | } 32 | -------------------------------------------------------------------------------- /shm/internal/test/shm_helper_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | 9 | "bitbucket.org/avd/go-ipc/shm" 10 | ) 11 | 12 | func newShmObject(name string, mode int, perm os.FileMode, typ string, size int) (shm.SharedMemoryObject, error) { 13 | switch typ { 14 | case "default", "": 15 | return shm.NewMemoryObject(name, mode, perm) 16 | case "wnm": 17 | return shm.NewWindowsNativeMemoryObject(name, mode, size) 18 | default: 19 | return nil, fmt.Errorf("unknown object type '%s'", typ) 20 | } 21 | } 22 | 23 | func destroyShmObject(name string, typ string) error { 24 | switch typ { 25 | case "default", "": 26 | return shm.DestroyMemoryObject(name) 27 | case "wnm": 28 | return nil 29 | default: 30 | return fmt.Errorf("unknown object type '%s'", typ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /fifo/fifo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package fifo 4 | 5 | import ( 6 | "io" 7 | "os" 8 | 9 | "bitbucket.org/avd/go-ipc/internal/common" 10 | ) 11 | 12 | const ( 13 | // O_NONBLOCK flag makes Fifo open operation nonblocking. 14 | O_NONBLOCK = common.O_NONBLOCK 15 | ) 16 | 17 | // Fifo represents a First-In-First-Out object 18 | type Fifo interface { 19 | io.ReadWriter 20 | io.Closer 21 | Destroy() error 22 | } 23 | 24 | // New creates or opens a new FIFO object 25 | // name - object name. 26 | // flag - flag is a combination of open flags from 'os' package along with O_NONBLOCK flag. 27 | // perm - object's permission bits. 28 | func New(name string, flag int, perm os.FileMode) (Fifo, error) { 29 | return newFifo(name, flag, perm) 30 | } 31 | 32 | // Destroy permanently removes the FIFO. 33 | func Destroy(name string) error { 34 | return destroyFifo(name) 35 | } 36 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Golang CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-go/ for more details 4 | version: 2 5 | jobs: 6 | build: 7 | docker: 8 | # specify the version 9 | - image: circleci/golang:1.11 10 | 11 | # Specify service dependencies here if necessary 12 | # CircleCI maintains a library of pre-built images 13 | # documented at https://circleci.com/docs/2.0/circleci-images/ 14 | # - image: circleci/postgres:9.4 15 | 16 | #### TEMPLATE_NOTE: go expects specific checkout path representing url 17 | #### expecting it in the form of 18 | #### /go/src/github.com/circleci/go-tool 19 | #### /go/src/bitbucket.org/circleci/go-tool 20 | working_directory: /go/src/bitbucket.org/avd/go-ipc 21 | steps: 22 | - checkout 23 | 24 | # specify any bash command here prefixed with `run: ` 25 | - run: go get -v -t -d ./... 26 | - run: go test -v ./... -------------------------------------------------------------------------------- /sync/internal/test/locker/locker_helper_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "sync" 8 | 9 | ipc_sync "bitbucket.org/avd/go-ipc/sync" 10 | ) 11 | 12 | func createLocker(typ, name string, mode int) (locker sync.Locker, err error) { 13 | switch typ { 14 | case "m": 15 | locker, err = ipc_sync.NewMutex(name, mode, 0666) 16 | case "spin": 17 | locker, err = ipc_sync.NewSpinMutex(name, mode, 0666) 18 | case "rw": 19 | locker, err = ipc_sync.NewRWMutex(name, mode, 0666) 20 | default: 21 | err = fmt.Errorf("unknown object type %q", typ) 22 | } 23 | return 24 | } 25 | 26 | func destroyLocker(typ, name string) error { 27 | switch typ { 28 | case "m": 29 | return ipc_sync.DestroyMutex(name) 30 | case "spin": 31 | return ipc_sync.DestroySpinMutex(name) 32 | case "rw": 33 | return ipc_sync.DestroyRWMutex(name) 34 | default: 35 | return fmt.Errorf("unknown object type %q", typ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 4 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 8 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 9 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 10 | golang.org/x/sys v0.0.0-20190507053917-2953c62de483 h1:0pONs62zZ8ED8kUnSCsv4RWjmwM6ideAalXGTybpo2s= 11 | golang.org/x/sys v0.0.0-20190507053917-2953c62de483/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 12 | -------------------------------------------------------------------------------- /internal/test/utils_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package testutil 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestBuildFilesFromOutput(t *testing.T) { 12 | type td struct { 13 | in string 14 | out []string 15 | } 16 | a := assert.New(t) 17 | data := []td{ 18 | { 19 | in: "[a.go]", 20 | out: []string{"a.go"}, 21 | }, 22 | { 23 | in: " [ a.go b.go] ", 24 | out: []string{"a.go", "b.go"}, 25 | }, 26 | { 27 | in: " [ a.go b.go c .go] ", 28 | out: []string{"a.go", "b.go", "c.go"}, 29 | }, 30 | { 31 | in: " [ a.go b.go c d .go] ", 32 | out: []string{"a.go", "b.go", "cd.go"}, 33 | }, 34 | { 35 | in: " [ a.go b.go c d ] ", 36 | out: []string{"a.go", "b.go", "cd"}, 37 | }, 38 | { 39 | in: " [ a b c d ] ", 40 | out: []string{"abcd"}, 41 | }, 42 | } 43 | for _, d := range data { 44 | a.Equal(d.out, buildFilesFromOutput(d.in)) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /sync/futex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build linux freebsd 4 | 5 | package sync 6 | 7 | import ( 8 | "math" 9 | "sync/atomic" 10 | "time" 11 | "unsafe" 12 | 13 | "golang.org/x/sys/unix" 14 | 15 | "bitbucket.org/avd/go-ipc/internal/common" 16 | ) 17 | 18 | const ( 19 | cFutexWakeAll = math.MaxInt32 20 | ) 21 | 22 | type futex struct { 23 | ptr unsafe.Pointer 24 | } 25 | 26 | func (w *futex) addr() *int32 { 27 | return (*int32)(w.ptr) 28 | } 29 | 30 | func (w *futex) add(value int) { 31 | atomic.AddInt32(w.addr(), int32(value)) 32 | } 33 | 34 | func (w *futex) wait(value int32, timeout time.Duration) error { 35 | err := FutexWait(w.ptr, value, timeout, 0) 36 | if err != nil && common.SyscallErrHasCode(err, unix.EWOULDBLOCK) { 37 | return nil 38 | } 39 | return err 40 | } 41 | 42 | func (w *futex) wake(count int32) (int, error) { 43 | return FutexWake(w.ptr, count, 0) 44 | } 45 | 46 | func (w *futex) wakeAll() (int, error) { 47 | return w.wake(cFutexWakeAll) 48 | } 49 | -------------------------------------------------------------------------------- /sync/internal/test/locker/locker_helper_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build linux freebsd darwin 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "sync" 10 | 11 | ipc_sync "bitbucket.org/avd/go-ipc/sync" 12 | ) 13 | 14 | func createLocker(typ, name string, flag int) (locker sync.Locker, err error) { 15 | switch typ { 16 | case "m": 17 | locker, err = ipc_sync.NewMutex(name, flag, 0666) 18 | case "msysv": 19 | locker, err = ipc_sync.NewSemaMutex(name, flag, 0666) 20 | case "spin": 21 | locker, err = ipc_sync.NewSpinMutex(name, flag, 0666) 22 | case "rw": 23 | locker, err = ipc_sync.NewRWMutex(name, flag, 0666) 24 | default: 25 | err = fmt.Errorf("unknown object type %q", typ) 26 | } 27 | return 28 | } 29 | 30 | func destroyLocker(typ, name string) error { 31 | switch typ { 32 | case "m": 33 | return ipc_sync.DestroyMutex(name) 34 | case "msysv": 35 | return ipc_sync.DestroySemaMutex(name) 36 | case "spin": 37 | return ipc_sync.DestroySpinMutex(name) 38 | case "rw": 39 | return ipc_sync.DestroyRWMutex(name) 40 | default: 41 | return fmt.Errorf("unknown object type %q", typ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sync/mutex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "io" 7 | "os" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | // IPCLocker is a minimal interface, which must be satisfied by any synchronization primitive on any platform. 13 | type IPCLocker interface { 14 | sync.Locker 15 | io.Closer 16 | } 17 | 18 | // TimedIPCLocker is a locker, whose lock operation can be limited with duration. 19 | type TimedIPCLocker interface { 20 | IPCLocker 21 | // LockTimeout tries to lock the locker, waiting for not more, than timeout 22 | LockTimeout(timeout time.Duration) bool 23 | } 24 | 25 | // NewMutex creates a new interprocess mutex. 26 | // It uses the default implementation on the current platform. 27 | // name - object name. 28 | // flag - flag is a combination of open flags from 'os' package. 29 | // perm - object's permission bits. 30 | func NewMutex(name string, flag int, perm os.FileMode) (TimedIPCLocker, error) { 31 | return newMutex(name, flag, perm) 32 | } 33 | 34 | // DestroyMutex permanently removes mutex with the given name. 35 | func DestroyMutex(name string) error { 36 | return destroyMutex(name) 37 | } 38 | 39 | func mutexSharedStateName(name, typ string) string { 40 | return name + ".s" + typ 41 | } 42 | -------------------------------------------------------------------------------- /internal/helper/helper.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package helper 4 | 5 | import ( 6 | "os" 7 | 8 | "bitbucket.org/avd/go-ipc/mmf" 9 | "bitbucket.org/avd/go-ipc/shm" 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | // CreateWritableRegion is a helper, which: 14 | // - creates a shared memory object with given parameters. 15 | // - creates a mapping for the entire region with mmf.MEM_READWRITE flag. 16 | // - closes memory object and returns memory region and a flag whether the object was created. 17 | func CreateWritableRegion(name string, flag int, perm os.FileMode, size int) (*mmf.MemoryRegion, bool, error) { 18 | obj, created, resultErr := shm.NewMemoryObjectSize(name, flag, perm, int64(size)) 19 | if resultErr != nil { 20 | return nil, false, errors.Wrap(resultErr, "failed to create shm object") 21 | } 22 | var region *mmf.MemoryRegion 23 | defer func() { 24 | obj.Close() 25 | if resultErr == nil { 26 | return 27 | } 28 | if region != nil { 29 | region.Close() 30 | } 31 | if created { 32 | obj.Destroy() 33 | } 34 | }() 35 | if region, resultErr = mmf.NewMemoryRegion(obj, mmf.MEM_READWRITE, 0, size); resultErr != nil { 36 | return nil, false, errors.Wrap(resultErr, "failed to create shm region") 37 | } 38 | return region, created, nil 39 | } 40 | -------------------------------------------------------------------------------- /mq/internal/test/mq_helper_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | 9 | "bitbucket.org/avd/go-ipc/mq" 10 | ) 11 | 12 | func createMqWithType(name string, perm os.FileMode, typ, opt string) (mq.Messenger, error) { 13 | switch typ { 14 | case "default", "fast": 15 | mqSize, msgSize := mq.DefaultFastMqMaxSize, mq.DefaultFastMqMessageSize 16 | if first, second, err := parseTwoInts(opt); err == nil { 17 | mqSize, msgSize = first, second 18 | } 19 | return mq.CreateFastMq(name, 0, perm, mqSize, msgSize) 20 | default: 21 | return nil, fmt.Errorf("unknown mq type %q", typ) 22 | } 23 | } 24 | 25 | func openMqWithType(name string, flags int, typ string) (mq.Messenger, error) { 26 | switch typ { 27 | case "default", "fast": 28 | return mq.OpenFastMq(name, flags) 29 | default: 30 | return nil, fmt.Errorf("unknown mq type %q", typ) 31 | } 32 | } 33 | 34 | func destroyMqWithType(name, typ string) error { 35 | switch typ { 36 | case "default", "fast": 37 | return mq.DestroyFastMq(name) 38 | default: 39 | return fmt.Errorf("unknown mq type %q", typ) 40 | } 41 | } 42 | 43 | func notifywait(name string, timeout int, typ string) error { 44 | return fmt.Errorf("notifywait is not supported on current platform") 45 | } 46 | -------------------------------------------------------------------------------- /sync/mutex_spin_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func spinCtor(name string, mode int, perm os.FileMode) (IPCLocker, error) { 11 | return NewSpinMutex(name, mode, perm) 12 | } 13 | 14 | func spinDtor(name string) error { 15 | return DestroySpinMutex(name) 16 | } 17 | 18 | func TestSpinMutexOpenMode(t *testing.T) { 19 | testLockerOpenMode(t, spinCtor, spinDtor) 20 | } 21 | 22 | func TestSpinMutexOpenMode2(t *testing.T) { 23 | testLockerOpenMode2(t, spinCtor, spinDtor) 24 | } 25 | 26 | func TestSpinMutexOpenMode3(t *testing.T) { 27 | testLockerOpenMode3(t, spinCtor, spinDtor) 28 | } 29 | 30 | func TestSpinMutexOpenMode4(t *testing.T) { 31 | testLockerOpenMode4(t, spinCtor, spinDtor) 32 | } 33 | 34 | func TestSpinMutexOpenMode5(t *testing.T) { 35 | testLockerOpenMode5(t, spinCtor, spinDtor) 36 | } 37 | 38 | func TestSpinMutexLock(t *testing.T) { 39 | testLockerLock(t, spinCtor, spinDtor) 40 | } 41 | 42 | func TestSpinMutexMemory(t *testing.T) { 43 | testLockerMemory(t, "spin", false, spinCtor, spinDtor) 44 | } 45 | 46 | func TestSpinMutexValueInc(t *testing.T) { 47 | testLockerValueInc(t, "spin", spinCtor, spinDtor) 48 | } 49 | 50 | func TestSpinMutexPanicsOnDoubleUnlock(t *testing.T) { 51 | testLockerTwiceUnlock(t, spinCtor, spinDtor) 52 | } 53 | -------------------------------------------------------------------------------- /mq/mq_fast_impl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package mq 4 | 5 | import ( 6 | "unsafe" 7 | 8 | "bitbucket.org/avd/go-ipc/internal/allocator" 9 | ) 10 | 11 | const ( 12 | fastMqHdrSize = int(unsafe.Sizeof(fastMqHdr{})) 13 | ) 14 | 15 | type fastMqHdr struct { 16 | blockedSenders int32 17 | blockedReceivers int32 18 | } 19 | 20 | type fastMq struct { 21 | header *fastMqHdr 22 | heap *sharedHeap 23 | } 24 | 25 | func newFastMq(data []byte, maxQueueSize, maxMsgSize int, created bool) *fastMq { 26 | rawData := allocator.ByteSliceData(data) 27 | result := &fastMq{header: (*fastMqHdr)(rawData)} 28 | rawData = allocator.AdvancePointer(rawData, uintptr(fastMqHdrSize)) 29 | if created { 30 | result.heap = newSharedHeap(rawData, maxQueueSize, maxMsgSize) 31 | result.header.blockedReceivers = 0 32 | result.header.blockedSenders = 0 33 | } else { 34 | result.heap = openSharedHeap(rawData) 35 | } 36 | return result 37 | } 38 | 39 | // calcFastMqSize returns number of bytes needed to store all messages and metadata. 40 | func calcFastMqSize(maxQueueSize, maxMsgSize int) (int, error) { 41 | sz, err := calcSharedHeapSize(maxQueueSize, maxMsgSize) 42 | if err != nil { 43 | return 0, err 44 | } 45 | return fastMqHdrSize + sz, nil 46 | } 47 | 48 | func minFastMqSize() int { 49 | return fastMqHdrSize + minHeapSize() 50 | } 51 | -------------------------------------------------------------------------------- /shm/shared_memory_linux_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package shm 4 | 5 | import ( 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestShmFsFromReader(t *testing.T) { 11 | const ( 12 | testData = ` 13 | # 14 | # /etc/fstab 15 | # name dir type opts freq passno 16 | UUID=cd459033-ae0a-4fb4-96fb-2323365a8e21 / ext4 defaults 1 1 17 | UUID=4542ef12-df3d-4336-9d12-740763854139 /boot ext4 defaults 1 2 18 | UUID=95bd9dce-550c-4622-9466-6cd1e1ffd278 /home ext4 defaults 1 2 19 | UUID=53d61062-7b6b-4f5b-80fd-7baf4017f96d swap swap defaults 0 0 20 | tmpfs /dev/shm tmpfs rw,seclabel,nosuid,nodev 0 0 21 | ` 22 | testData2 = "tmpfs /dev/shm nottmpfs rw,seclabel,nosuid,nodev 0 0" 23 | ) 24 | path := shmFsFromReader(strings.NewReader(testData)) 25 | if path != "/dev/shm/" { 26 | t.Errorf("shm mountpoints not parsed. expected '/dev/shm/', got '%s'", path) 27 | } 28 | path = shmFsFromReader(strings.NewReader(testData2)) 29 | if path != "" { 30 | t.Errorf("shm mountpoint should not be parsed. got '%s'", path) 31 | } 32 | } 33 | 34 | func TestShmFsFromMountPoints(t *testing.T) { 35 | path := shmFsFromMounts() 36 | if len(path) == 0 { 37 | t.Errorf("couldn't find a correct shm path") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Release 0.6.1 (2019-05-07) 2 | 3 | - vendor: update golang.org/x/sys. 4 | - fifo: fix tests in windows. 5 | - sync: fix compiation issue on windows with latest golang.org/x/sys. 6 | 7 | # Release 0.6.0 (2018-10-29) 8 | 9 | - vendor: gomod support. 10 | - ipc: remove Destroyer interface. 11 | 12 | # Release 0.5.0 (2016-12-29) 13 | 14 | - sync: condvar on darwin now uses semaphore-based events. 15 | - sync: events optimization with lightweight event. 16 | - sync: timed mutex for darwin. 17 | - sync: timed semaphore for freebsd/darwin. 18 | - minimum go version is 1.4. 19 | 20 | # Release 0.4.0 (2016-12-18) 21 | 22 | - sync: RWMutex for all platforms. 23 | - sync: Semaphore for all platforms. 24 | - mq: FastMq has become about 30% faster. 25 | - all: Added examples. 26 | - sync: Event for all platforms. 27 | 28 | # Release 0.3.0 (2016-10-12) 29 | 30 | - sync: Limited condvar support on darwin and windows via spin waiters. 31 | - mq: FastMq blocking mode for darwin, windows. 32 | - sync: Default mutex implementation on freebsd uses futex via umtx syscall. 33 | 34 | # Release 0.2.0 (2016-06-22) 35 | 36 | - Added FastMq implementation. FastMq is a priority queue based on shared memory. See mq package docs for details. 37 | - Huge windows native shm refactor. It has new API now. 38 | - Improved errors reporting: use github.com/pkg/errors to wrap errors. 39 | 40 | # Release 0.1.0 (2016-05-24) 41 | 42 | - Initial release. -------------------------------------------------------------------------------- /sync/sema_sys.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build linux,amd64 darwin freebsd 4 | 5 | package sync 6 | 7 | import ( 8 | "os" 9 | "syscall" 10 | "unsafe" 11 | 12 | "bitbucket.org/avd/go-ipc/internal/allocator" 13 | "bitbucket.org/avd/go-ipc/internal/common" 14 | 15 | "golang.org/x/sys/unix" 16 | ) 17 | 18 | var ( 19 | sysSemGet uintptr 20 | sysSemCtl uintptr 21 | sysSemOp uintptr 22 | ) 23 | 24 | func semget(k common.Key, nsems, semflg int) (int, error) { 25 | id, _, err := unix.Syscall(sysSemGet, uintptr(k), uintptr(nsems), uintptr(semflg)) 26 | if err != syscall.Errno(0) { 27 | if err == unix.EEXIST || err == unix.ENOENT { 28 | return 0, &os.PathError{Op: "SEMGET", Path: "", Err: err} 29 | } 30 | return 0, os.NewSyscallError("SEMGET", err) 31 | } 32 | return int(id), nil 33 | } 34 | 35 | func semctl(id, num, cmd int) error { 36 | _, _, err := unix.Syscall(sysSemCtl, uintptr(id), uintptr(num), uintptr(cmd)) 37 | if err != syscall.Errno(0) { 38 | return os.NewSyscallError("SEMCTL", err) 39 | } 40 | return nil 41 | } 42 | 43 | func semop(id int, ops []sembuf) error { 44 | if len(ops) == 0 { 45 | return nil 46 | } 47 | pOps := unsafe.Pointer(&ops[0]) 48 | _, _, err := unix.Syscall(sysSemOp, uintptr(id), uintptr(pOps), uintptr(len(ops))) 49 | allocator.Use(pOps) 50 | if err != syscall.Errno(0) { 51 | return os.NewSyscallError("SEMOP", err) 52 | } 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /sync/mutex_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func mutexCtor(name string, flag int, perm os.FileMode) (IPCLocker, error) { 11 | return NewMutex(name, flag, perm) 12 | } 13 | 14 | func mutexDtor(name string) error { 15 | return DestroyMutex(name) 16 | } 17 | 18 | func TestMutexOpenMode(t *testing.T) { 19 | testLockerOpenMode(t, mutexCtor, mutexDtor) 20 | } 21 | 22 | func TestMutexOpenMode2(t *testing.T) { 23 | testLockerOpenMode2(t, mutexCtor, mutexDtor) 24 | } 25 | 26 | func TestMutexOpenMode3(t *testing.T) { 27 | testLockerOpenMode3(t, mutexCtor, mutexDtor) 28 | } 29 | 30 | func TestMutexOpenMode4(t *testing.T) { 31 | testLockerOpenMode4(t, mutexCtor, mutexDtor) 32 | } 33 | 34 | func TestMutexOpenMode5(t *testing.T) { 35 | testLockerOpenMode5(t, mutexCtor, mutexDtor) 36 | } 37 | 38 | func TestMutexLock(t *testing.T) { 39 | testLockerLock(t, mutexCtor, mutexDtor) 40 | } 41 | 42 | func TestMutexMemory(t *testing.T) { 43 | testLockerMemory(t, defaultMutexType, false, mutexCtor, mutexDtor) 44 | } 45 | 46 | func TestMutexValueInc(t *testing.T) { 47 | testLockerValueInc(t, defaultMutexType, mutexCtor, mutexDtor) 48 | } 49 | 50 | func TestMutexLockTimeout(t *testing.T) { 51 | testLockerLockTimeout(t, defaultMutexType, mutexCtor, mutexDtor) 52 | } 53 | 54 | func TestMutexLockTimeout2(t *testing.T) { 55 | testLockerLockTimeout2(t, defaultMutexType, mutexCtor, mutexDtor) 56 | } 57 | 58 | func TestMutexPanicsOnDoubleUnlock(t *testing.T) { 59 | testLockerTwiceUnlock(t, mutexCtor, mutexDtor) 60 | } 61 | -------------------------------------------------------------------------------- /internal/array/mapped_array.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package array 4 | 5 | import ( 6 | "sync/atomic" 7 | "unsafe" 8 | 9 | "bitbucket.org/avd/go-ipc/internal/allocator" 10 | ) 11 | 12 | const ( 13 | mappedArrayHdrSize = unsafe.Sizeof(mappedArray{}) 14 | ) 15 | 16 | type mappedArray struct { 17 | capacity int32 18 | elemSize int32 19 | size int32 20 | dummyDataArray [0]byte 21 | } 22 | 23 | func newMappedArray(pointer unsafe.Pointer) *mappedArray { 24 | return (*mappedArray)(pointer) 25 | } 26 | 27 | func (arr *mappedArray) init(capacity, elemSize int) { 28 | arr.capacity = int32(capacity) 29 | arr.elemSize = int32(elemSize) 30 | arr.size = 0 31 | } 32 | 33 | func (arr *mappedArray) elemLen() int { 34 | return int(arr.elemSize) 35 | } 36 | 37 | func (arr *mappedArray) cap() int { 38 | return int(arr.capacity) 39 | } 40 | 41 | func (arr *mappedArray) len() int { 42 | return int(arr.size) 43 | } 44 | 45 | func (arr *mappedArray) safeLen() int { 46 | return int(atomic.LoadInt32(&arr.size)) 47 | } 48 | 49 | func (arr *mappedArray) incLen() { 50 | atomic.AddInt32(&arr.size, 1) 51 | } 52 | 53 | func (arr *mappedArray) decLen() { 54 | atomic.AddInt32(&arr.size, -1) 55 | } 56 | 57 | func (arr *mappedArray) atPointer(idx int) unsafe.Pointer { 58 | return allocator.AdvancePointer(unsafe.Pointer(&arr.dummyDataArray), uintptr(idx*int(arr.elemSize))) 59 | } 60 | 61 | func (arr *mappedArray) at(idx int) []byte { 62 | slotsPtr := arr.atPointer(idx) 63 | return allocator.ByteSliceFromUnsafePointer(slotsPtr, int(arr.elemSize), int(arr.elemSize)) 64 | } 65 | -------------------------------------------------------------------------------- /mmf/reader_writer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package mmf 4 | 5 | import ( 6 | "bytes" 7 | "io" 8 | ) 9 | 10 | // MemoryRegionReader is a reader for safe operations over a shared memory region. 11 | // It holds a reference to the region, so the former can't be gc'ed. 12 | type MemoryRegionReader struct { 13 | region *MemoryRegion 14 | *bytes.Reader 15 | } 16 | 17 | // NewMemoryRegionReader creates a new reader for the given region. 18 | func NewMemoryRegionReader(region *MemoryRegion) *MemoryRegionReader { 19 | return &MemoryRegionReader{ 20 | region: region, 21 | Reader: bytes.NewReader(region.Data()), 22 | } 23 | } 24 | 25 | // MemoryRegionWriter is a writer for safe operations over a shared memory region. 26 | // It holds a reference to the region, so the former can't be gc'ed. 27 | type MemoryRegionWriter struct { 28 | region *MemoryRegion 29 | pos int64 30 | } 31 | 32 | // NewMemoryRegionWriter creates a new writer for the given region. 33 | func NewMemoryRegionWriter(region *MemoryRegion) *MemoryRegionWriter { 34 | return &MemoryRegionWriter{region: region} 35 | } 36 | 37 | // WriteAt is to implement io.WriterAt. 38 | func (w *MemoryRegionWriter) WriteAt(p []byte, off int64) (n int, err error) { 39 | data := w.region.Data() 40 | n = len(data) - int(off) 41 | if n > 0 { 42 | if n > len(p) { 43 | n = len(p) 44 | } 45 | copy(data[off:], p[:n]) 46 | } 47 | if n < len(p) { 48 | err = io.EOF 49 | } 50 | return 51 | } 52 | 53 | // Write is to implement io.Writer. 54 | func (w *MemoryRegionWriter) Write(p []byte) (n int, err error) { 55 | n, err = w.WriteAt(p, w.pos) 56 | w.pos += int64(n) 57 | return n, err 58 | } 59 | -------------------------------------------------------------------------------- /mq/internal/test/mq_helper_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build darwin freebsd 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | 11 | "bitbucket.org/avd/go-ipc/mq" 12 | ) 13 | 14 | func createMqWithType(name string, perm os.FileMode, typ, opt string) (mq.Messenger, error) { 15 | switch typ { 16 | case "default": 17 | return mq.New(name, os.O_RDWR, perm) 18 | case "fast": 19 | mqSize, msgSize := mq.DefaultFastMqMaxSize, mq.DefaultFastMqMessageSize 20 | if first, second, err := parseTwoInts(opt); err == nil { 21 | mqSize, msgSize = first, second 22 | } 23 | return mq.CreateFastMq(name, 0, perm, mqSize, msgSize) 24 | case "sysv": 25 | return mq.CreateSystemVMessageQueue(name, 0, perm) 26 | default: 27 | return nil, fmt.Errorf("unknown mq type %q", typ) 28 | } 29 | } 30 | 31 | func openMqWithType(name string, flags int, typ string) (mq.Messenger, error) { 32 | switch typ { 33 | case "default": 34 | return mq.Open(name, flags) 35 | case "fast": 36 | return mq.OpenFastMq(name, flags) 37 | case "sysv": 38 | return mq.OpenSystemVMessageQueue(name, flags) 39 | default: 40 | return nil, fmt.Errorf("unknown mq type %q", typ) 41 | } 42 | } 43 | 44 | func destroyMqWithType(name, typ string) error { 45 | switch typ { 46 | case "default": 47 | return mq.Destroy(name) 48 | case "fast": 49 | return mq.DestroyFastMq(name) 50 | case "sysv": 51 | return mq.DestroySystemVMessageQueue(name) 52 | default: 53 | return fmt.Errorf("unknown mq type %q", typ) 54 | } 55 | } 56 | 57 | func notifywait(name string, timeout int, typ string) error { 58 | return fmt.Errorf("notifywait is not supported on current platform") 59 | } 60 | -------------------------------------------------------------------------------- /sync/cond_event.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "sync/atomic" 9 | "time" 10 | "unsafe" 11 | 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | const ( 16 | condWaiterSize = 8 17 | ) 18 | 19 | var ( 20 | pid = os.Getpid() 21 | seq uint32 22 | ) 23 | 24 | type waiter struct { 25 | id *uint64 26 | e *Event 27 | } 28 | 29 | func nextID() uint32 { 30 | return atomic.AddUint32(&seq, 1) 31 | } 32 | 33 | func newWaiter(ptr unsafe.Pointer) *waiter { 34 | for { 35 | id := uint64(pid)<<32 | uint64(nextID()) 36 | e, err := NewEvent(condWaiterEventName(id), os.O_CREATE|os.O_EXCL, 0666, false) 37 | if err == nil { 38 | result := &waiter{id: (*uint64)(ptr), e: e} 39 | *result.id = id 40 | return result 41 | } 42 | if !os.IsExist(errors.Cause(err)) { 43 | panic(errors.Wrap(err, "cond: failed to create an event")) 44 | } 45 | } 46 | } 47 | 48 | func openWaiter(ptr unsafe.Pointer) *waiter { 49 | return &waiter{id: (*uint64)(ptr)} 50 | } 51 | 52 | func (w *waiter) signal() bool { 53 | ev, err := NewEvent(condWaiterEventName(*w.id), 0, 0, false) 54 | if err != nil { 55 | if os.IsNotExist(errors.Cause(err)) { 56 | return false 57 | } 58 | panic(err) 59 | } 60 | ev.Set() 61 | ev.Close() 62 | return true 63 | } 64 | 65 | func (w *waiter) isSame(ptr unsafe.Pointer) bool { 66 | return unsafe.Pointer(w.id) == ptr 67 | } 68 | 69 | func (w *waiter) destroy() { 70 | w.e.Destroy() 71 | } 72 | 73 | func (w *waiter) waitTimeout(timeout time.Duration) bool { 74 | return w.e.WaitTimeout(timeout) 75 | } 76 | 77 | func condWaiterEventName(id uint64) string { 78 | return fmt.Sprintf("cev.%d", id) 79 | } 80 | -------------------------------------------------------------------------------- /sync/mutex_sysv_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build linux freebsd 4 | 5 | package sync 6 | 7 | import ( 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func sysvMutexCtor(name string, flag int, perm os.FileMode) (IPCLocker, error) { 13 | return NewSemaMutex(name, flag, perm) 14 | } 15 | 16 | func sysvMutexDtor(name string) error { 17 | return DestroySemaMutex(name) 18 | } 19 | 20 | func TestSysvMutexOpenMode(t *testing.T) { 21 | testLockerOpenMode(t, sysvMutexCtor, sysvMutexDtor) 22 | } 23 | 24 | func TestSysvMutexOpenMode2(t *testing.T) { 25 | testLockerOpenMode2(t, sysvMutexCtor, sysvMutexDtor) 26 | } 27 | 28 | func TestSysvMutexOpenMode3(t *testing.T) { 29 | testLockerOpenMode3(t, sysvMutexCtor, sysvMutexDtor) 30 | } 31 | 32 | func TestSysvMutexOpenMode4(t *testing.T) { 33 | testLockerOpenMode4(t, sysvMutexCtor, sysvMutexDtor) 34 | } 35 | 36 | func TestSysvMutexOpenMode5(t *testing.T) { 37 | testLockerOpenMode5(t, sysvMutexCtor, sysvMutexDtor) 38 | } 39 | 40 | func TestSysvMutexLock(t *testing.T) { 41 | testLockerLock(t, sysvMutexCtor, sysvMutexDtor) 42 | } 43 | 44 | func TestSysvMutexMemory(t *testing.T) { 45 | testLockerMemory(t, "msysv", false, sysvMutexCtor, sysvMutexDtor) 46 | } 47 | 48 | func TestSysvMutexValueInc(t *testing.T) { 49 | testLockerValueInc(t, "msysv", sysvMutexCtor, sysvMutexDtor) 50 | } 51 | 52 | func TestSysvMutexLockTimeout(t *testing.T) { 53 | testLockerLockTimeout(t, "msysv", sysvMutexCtor, sysvMutexDtor) 54 | } 55 | 56 | func TestSysvMutexLockTimeout2(t *testing.T) { 57 | testLockerLockTimeout2(t, "msysv", sysvMutexCtor, sysvMutexDtor) 58 | } 59 | 60 | func TestSysvMutexPanicsOnDoubleUnlock(t *testing.T) { 61 | testLockerTwiceUnlock(t, sysvMutexCtor, sysvMutexDtor) 62 | } 63 | -------------------------------------------------------------------------------- /sync/cond_spin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | //+build ignore 4 | 5 | package sync 6 | 7 | import ( 8 | "runtime" 9 | "sync/atomic" 10 | "time" 11 | "unsafe" 12 | ) 13 | 14 | const ( 15 | condWaiterSize = 4 16 | 17 | cSpinWaiterUnset = 0 18 | cSpinWaiterWaitDone = 1 19 | cSpinWaiterSet = 2 20 | ) 21 | 22 | type waiter uint32 23 | 24 | func newWaiter(ptr unsafe.Pointer) *waiter { 25 | w := (*waiter)(ptr) 26 | *w = cSpinWaiterUnset 27 | return w 28 | } 29 | 30 | func openWaiter(ptr unsafe.Pointer) *waiter { 31 | return (*waiter)(ptr) 32 | } 33 | 34 | // signal wakes a spin waiter. 35 | func (w *waiter) signal() (signaled bool) { 36 | return atomic.CompareAndSwapUint32((*uint32)(w), cSpinWaiterUnset, cSpinWaiterSet) 37 | } 38 | 39 | func (w *waiter) waitTimeout(timeout time.Duration) bool { 40 | var attempt uint64 41 | start := time.Now() 42 | ptr := (*uint32)(w) 43 | for !atomic.CompareAndSwapUint32(ptr, cSpinWaiterSet, cSpinWaiterWaitDone) { 44 | if attempt%1000 == 0 { // do not call time.Since too often. 45 | if timeout >= 0 && time.Since(start) >= timeout { 46 | // if we changed the value from 'unset' to 'done', than the waiter had not been set, return false. 47 | // otherwise, the value is 'set', we consider waiting to be successful and return true. 48 | ret := !atomic.CompareAndSwapUint32(ptr, cSpinWaiterUnset, cSpinWaiterWaitDone) 49 | if ret { 50 | atomic.StoreUint32(ptr, cSpinWaiterWaitDone) 51 | } 52 | return ret 53 | } 54 | runtime.Gosched() 55 | } 56 | attempt++ 57 | } 58 | return true 59 | } 60 | 61 | func (w *waiter) isSame(ptr unsafe.Pointer) bool { 62 | return unsafe.Pointer(w) == ptr 63 | } 64 | 65 | // destroy is a no-op for a spin waiter. 66 | func (w *waiter) destroy() {} 67 | -------------------------------------------------------------------------------- /sync/internal/test/event/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "os" 9 | "time" 10 | 11 | "bitbucket.org/avd/go-ipc/sync" 12 | ) 13 | 14 | var ( 15 | timeout = flag.Int("timeout", -1, "timeout for wait, in ms.") 16 | fail = flag.Bool("fail", false, "operation must fail") 17 | ) 18 | 19 | const usage = ` test program for events. 20 | available commands: 21 | wait event_name 22 | set event_name 23 | ` 24 | 25 | func wait() error { 26 | if flag.NArg() != 2 { 27 | return fmt.Errorf("wait: must provide event name only") 28 | } 29 | ev, err := sync.NewEvent(flag.Arg(1), 0, 0666, false) 30 | if err != nil { 31 | return err 32 | } 33 | if *timeout < 0 { 34 | ev.Wait() 35 | } else { 36 | ok := ev.WaitTimeout(time.Duration(*timeout) * time.Millisecond) 37 | if ok != !*fail { 38 | if !ok { 39 | return fmt.Errorf("timeout exceeded") 40 | } 41 | return fmt.Errorf("timeout passed") 42 | } 43 | } 44 | return ev.Close() 45 | } 46 | 47 | func set() error { 48 | if flag.NArg() != 2 { 49 | return fmt.Errorf("signal: must provide event name only") 50 | } 51 | ev, err := sync.NewEvent(flag.Arg(1), 0, 0666, false) 52 | if err != nil { 53 | return err 54 | } 55 | ev.Set() 56 | return ev.Close() 57 | } 58 | 59 | func runCommand() error { 60 | command := flag.Arg(0) 61 | switch command { 62 | case "wait": 63 | return wait() 64 | case "set": 65 | return set() 66 | default: 67 | return fmt.Errorf("unknown command") 68 | } 69 | } 70 | 71 | func main() { 72 | flag.Parse() 73 | if flag.NArg() == 0 { 74 | fmt.Print(usage) 75 | flag.Usage() 76 | os.Exit(1) 77 | } 78 | if err := runCommand(); err != nil { 79 | fmt.Fprintf(os.Stderr, "%v\n", err) 80 | os.Exit(1) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /sync/event.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "os" 7 | "time" 8 | ) 9 | 10 | // Event is a synchronization primitive used for notification. 11 | // If it is signaled by a call to Set(), it'll stay in this state, 12 | // unless someone calls Wait(). After it the event is reset into non-signaled state. 13 | type Event event 14 | 15 | // NewEvent creates a new interprocess event. 16 | // It uses the default implementation on the current platform. 17 | // name - object name. 18 | // flag - flag is a combination of open flags from 'os' package. 19 | // perm - object's permission bits. 20 | // initial - if true, the event will be set after creation. 21 | func NewEvent(name string, flag int, perm os.FileMode, initial bool) (*Event, error) { 22 | e, err := newEvent(name, flag, perm, initial) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return (*Event)(e), nil 27 | } 28 | 29 | // Set sets the specified event object to the signaled state. 30 | func (e *Event) Set() { 31 | (*event)(e).set() 32 | } 33 | 34 | // Wait waits for the event to be signaled. 35 | func (e *Event) Wait() { 36 | (*event)(e).wait() 37 | } 38 | 39 | // WaitTimeout waits until the event is signaled or the timeout elapses. 40 | func (e *Event) WaitTimeout(timeout time.Duration) bool { 41 | return (*event)(e).waitTimeout(timeout) 42 | } 43 | 44 | // Close closes the event. 45 | func (e *Event) Close() error { 46 | return (*event)(e).close() 47 | } 48 | 49 | // Destroy permanently destroys the event. 50 | func (e *Event) Destroy() error { 51 | return (*event)(e).destroy() 52 | } 53 | 54 | // DestroyEvent permanently destroys an event with the given name. 55 | func DestroyEvent(name string) error { 56 | return destroyEvent(name) 57 | } 58 | 59 | func eventName(baseName string) string { 60 | return baseName + ".ev" 61 | } 62 | -------------------------------------------------------------------------------- /sync/event_futex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build freebsd linux 4 | 5 | package sync 6 | 7 | import ( 8 | "os" 9 | "time" 10 | 11 | "bitbucket.org/avd/go-ipc/internal/allocator" 12 | "bitbucket.org/avd/go-ipc/internal/helper" 13 | "bitbucket.org/avd/go-ipc/mmf" 14 | "bitbucket.org/avd/go-ipc/shm" 15 | "github.com/pkg/errors" 16 | ) 17 | 18 | type event struct { 19 | name string 20 | region *mmf.MemoryRegion 21 | lwe *lwEvent 22 | } 23 | 24 | func newEvent(name string, flag int, perm os.FileMode, initial bool) (*event, error) { 25 | if err := ensureOpenFlags(flag); err != nil { 26 | return nil, err 27 | } 28 | 29 | region, created, err := helper.CreateWritableRegion(eventName(name), flag, perm, lweStateSize) 30 | if err != nil { 31 | return nil, errors.Wrap(err, "failed to create shared state") 32 | } 33 | state := allocator.ByteSliceData(region.Data()) 34 | result := &event{ 35 | lwe: newLightweightEvent(state, &futex{ptr: state}), 36 | name: name, 37 | region: region, 38 | } 39 | if created { 40 | result.lwe.init(initial) 41 | } 42 | return result, nil 43 | } 44 | 45 | func (e *event) set() { 46 | e.lwe.set() 47 | } 48 | 49 | func (e *event) wait() { 50 | e.waitTimeout(-1) 51 | } 52 | 53 | func (e *event) waitTimeout(timeout time.Duration) bool { 54 | return e.lwe.waitTimeout(timeout) 55 | } 56 | 57 | func (e *event) close() error { 58 | return e.region.Close() 59 | } 60 | 61 | func (e *event) destroy() error { 62 | if err := e.close(); err != nil { 63 | return errors.Wrap(err, "failed to close shm region") 64 | } 65 | return destroyEvent(e.name) 66 | } 67 | 68 | func destroyEvent(name string) error { 69 | err := shm.DestroyMemoryObject(eventName(name)) 70 | if err != nil { 71 | return errors.Wrap(err, "failed to destroy memory object") 72 | } 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /sync/sema_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "os" 7 | "time" 8 | 9 | "github.com/pkg/errors" 10 | 11 | "golang.org/x/sys/windows" 12 | ) 13 | 14 | // semaphore is a platform specific semaphore implementation. 15 | // on windows it uses system semaphore object. 16 | type semaphore struct { 17 | handle windows.Handle 18 | } 19 | 20 | func newSemaphore(name string, flag int, perm os.FileMode, initial int) (*semaphore, error) { 21 | if err := ensureOpenFlags(flag); err != nil { 22 | return nil, err 23 | } 24 | handle, err := openOrCreateSemaphore(name, flag, initial, CSemMaxVal) 25 | if err != nil { 26 | return nil, errors.Wrap(err, "failed to open/create semaphore") 27 | } 28 | return &semaphore{handle: handle}, nil 29 | } 30 | 31 | func (s *semaphore) close() error { 32 | if err := windows.CloseHandle(s.handle); err != nil { 33 | return errors.Wrap(err, "failed to close windows handle") 34 | } 35 | return nil 36 | } 37 | 38 | func (s *semaphore) signal(count int) { 39 | if _, err := sys_ReleaseSemaphore(s.handle, count); err != nil { 40 | panic(err) 41 | } 42 | } 43 | 44 | func (s *semaphore) wait() { 45 | s.waitTimeout(-1) 46 | } 47 | 48 | func (s *semaphore) waitTimeout(timeout time.Duration) bool { 49 | waitMillis := uint32(windows.INFINITE) 50 | if timeout >= 0 { 51 | waitMillis = uint32(timeout.Nanoseconds() / 1e6) 52 | } 53 | ev, err := windows.WaitForSingleObject(s.handle, waitMillis) 54 | switch ev { 55 | case windows.WAIT_OBJECT_0: 56 | return true 57 | case uint32(windows.WAIT_TIMEOUT): 58 | return false 59 | default: 60 | if err != nil { 61 | panic(err) 62 | } else { 63 | panic(errors.Errorf("invalid wait state for an event: %d", ev)) 64 | } 65 | } 66 | } 67 | 68 | // destroySemaphore is a no-op on windows. 69 | func destroySemaphore(name string) error { 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /mq/mq_sysv_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build darwin freebsd linux 4 | 5 | package mq 6 | 7 | import ( 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func sysVMqCtor(name string, flag int, perm os.FileMode) (Messenger, error) { 13 | return CreateSystemVMessageQueue(name, flag, perm) 14 | } 15 | 16 | func sysVMqOpener(name string, flags int) (Messenger, error) { 17 | return OpenSystemVMessageQueue(name, flags) 18 | } 19 | 20 | func sysVMqDtor(name string) error { 21 | return DestroySystemVMessageQueue(name) 22 | } 23 | 24 | func TestCreateSysVMq(t *testing.T) { 25 | testCreateMq(t, sysVMqCtor, sysVMqDtor) 26 | } 27 | 28 | func TestCreateSysVMqExcl(t *testing.T) { 29 | testCreateMqExcl(t, sysVMqCtor, sysVMqDtor) 30 | } 31 | 32 | func TestCreateSysVMqInvalidPerm(t *testing.T) { 33 | testCreateMqInvalidPerm(t, sysVMqCtor, sysVMqDtor) 34 | } 35 | 36 | func TestOpenSysVMq(t *testing.T) { 37 | testOpenMq(t, sysVMqCtor, sysVMqOpener, sysVMqDtor) 38 | } 39 | 40 | func TestSysVMqSendIntSameProcess(t *testing.T) { 41 | testMqSendIntSameProcess(t, sysVMqCtor, sysVMqOpener, sysVMqDtor) 42 | } 43 | 44 | func TestSysVMqSendStructSameProcess(t *testing.T) { 45 | testMqSendStructSameProcess(t, sysVMqCtor, sysVMqOpener, sysVMqDtor) 46 | } 47 | 48 | func TestSysVMqSendMessageLessThenBuffer(t *testing.T) { 49 | testMqSendMessageLessThenBuffer(t, sysVMqCtor, sysVMqOpener, sysVMqDtor) 50 | } 51 | 52 | func TestSysVMqSendNonBlock(t *testing.T) { 53 | testMqSendNonBlock(t, sysVMqCtor, sysVMqDtor) 54 | } 55 | 56 | func TestSysVMqSendToAnotherProcess(t *testing.T) { 57 | testMqSendToAnotherProcess(t, sysVMqCtor, sysVMqDtor, "sysv") 58 | } 59 | 60 | func TestSysVMqReceiveNonBlock(t *testing.T) { 61 | testMqReceiveNonBlock(t, sysVMqCtor, sysVMqDtor) 62 | } 63 | 64 | func TestSysVMqReceiveFromAnotherProcess(t *testing.T) { 65 | testMqReceiveFromAnotherProcess(t, sysVMqCtor, sysVMqDtor, "sysv") 66 | } 67 | -------------------------------------------------------------------------------- /sync/internal/test/sema/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "os" 9 | "strconv" 10 | "time" 11 | 12 | "bitbucket.org/avd/go-ipc/sync" 13 | ) 14 | 15 | var ( 16 | timeout = flag.Int("timeout", -1, "timeout for wait, in ms.") 17 | fail = flag.Bool("fail", false, "operation must fail") 18 | ) 19 | 20 | const usage = ` test program for semaphores. 21 | available commands: 22 | wait sema_name 23 | signal sema_name count 24 | ` 25 | 26 | func wait() error { 27 | if flag.NArg() != 2 { 28 | return fmt.Errorf("wait: must provide sema name only") 29 | } 30 | s, err := sync.NewSemaphore(flag.Arg(1), 0, 0666, 0) 31 | if err != nil { 32 | return err 33 | } 34 | if *timeout < 0 { 35 | s.Wait() 36 | } else { 37 | ok := s.WaitTimeout(time.Duration(*timeout) * time.Millisecond) 38 | if ok != !*fail { 39 | if !ok { 40 | return fmt.Errorf("timeout exceeded") 41 | } 42 | return fmt.Errorf("timeout passed") 43 | } 44 | } 45 | return s.Close() 46 | } 47 | 48 | func signal() error { 49 | if flag.NArg() != 3 { 50 | return fmt.Errorf("signal: must provide sema name and count") 51 | } 52 | s, err := sync.NewSemaphore(flag.Arg(1), 0, 0666, 0) 53 | if err != nil { 54 | return err 55 | } 56 | count, err := strconv.Atoi(flag.Arg(2)) 57 | if err != nil { 58 | return err 59 | } 60 | s.Signal(count) 61 | return s.Close() 62 | } 63 | 64 | func runCommand() error { 65 | command := flag.Arg(0) 66 | switch command { 67 | case "wait": 68 | return wait() 69 | case "signal": 70 | return signal() 71 | default: 72 | return fmt.Errorf("unknown command") 73 | } 74 | } 75 | 76 | func main() { 77 | flag.Parse() 78 | if flag.NArg() == 0 { 79 | fmt.Print(usage) 80 | flag.Usage() 81 | os.Exit(1) 82 | } 83 | if err := runCommand(); err != nil { 84 | fmt.Fprintf(os.Stderr, "%v\n", err) 85 | os.Exit(1) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /sync/sys_sema_linux_386.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "os" 7 | "syscall" 8 | "unsafe" 9 | 10 | "bitbucket.org/avd/go-ipc/internal/allocator" 11 | "bitbucket.org/avd/go-ipc/internal/common" 12 | 13 | "golang.org/x/sys/unix" 14 | ) 15 | 16 | const ( 17 | cSEMOP = 1 18 | cSEMGET = 2 19 | cSEMCTL = 3 20 | cSEMTIMEDOP = 4 21 | ) 22 | 23 | // semun is a union used in semctl syscall. is not not actually used, so its size 24 | // matches the size in kernel. we need one only global readonly pointer to it. 25 | type semun struct { 26 | unused uintptr 27 | } 28 | 29 | var ( 30 | semun_inst = unsafe.Pointer(&semun{}) 31 | ) 32 | 33 | func semget(k common.Key, nsems, semflg int) (int, error) { 34 | id, _, err := unix.Syscall6(unix.SYS_IPC, cSEMGET, uintptr(k), uintptr(nsems), uintptr(semflg), 0, 0) 35 | if err != syscall.Errno(0) { 36 | if err == unix.EEXIST || err == unix.ENOENT { 37 | return 0, &os.PathError{Op: "SEMGET", Path: "", Err: err} 38 | } 39 | return 0, err 40 | } 41 | return int(id), nil 42 | } 43 | 44 | func semctl(id, num, cmd int) error { 45 | _, _, err := unix.Syscall6(unix.SYS_IPC, cSEMCTL, uintptr(id), uintptr(num), uintptr(cmd), uintptr(semun_inst), 0) 46 | if err != syscall.Errno(0) { 47 | return os.NewSyscallError("SEMCTL", err) 48 | } 49 | return nil 50 | } 51 | 52 | func semop(id int, ops []sembuf) error { 53 | return semtimedop(id, ops, nil) 54 | } 55 | 56 | func semtimedop(id int, ops []sembuf, timeout *unix.Timespec) error { 57 | if len(ops) == 0 { 58 | return nil 59 | } 60 | pOps := unsafe.Pointer(&ops[0]) 61 | pTimeout := unsafe.Pointer(timeout) 62 | _, _, err := unix.Syscall6(unix.SYS_IPC, cSEMTIMEDOP, uintptr(id), uintptr(len(ops)), 0, uintptr(pOps), uintptr(pTimeout)) 63 | allocator.Use(pOps) 64 | allocator.Use(pTimeout) 65 | if err != syscall.Errno(0) { 66 | return os.NewSyscallError("SEMTIMEDOP", err) 67 | } 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /sync/cond.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "errors" 7 | "os" 8 | "time" 9 | ) 10 | 11 | var ( 12 | // MaxCondWaiters is the maximum length of the waiting queue for this type of a cond. 13 | // This limit is actual for waitlist-based condvars, currently on windows and darwin. 14 | // If this limit is exceeded, Wait/WaitTimeout will panic with ErrTooManyWaiters. 15 | MaxCondWaiters = 128 16 | // ErrTooManyWaiters is an error, that indicates, that the waiting queue is full. 17 | ErrTooManyWaiters = errors.New("waiters limit has been reached") 18 | ) 19 | 20 | // Cond is a named interprocess condition variable. 21 | type Cond cond 22 | 23 | // NewCond returns new interprocess condvar. 24 | // name - unique condvar name. 25 | // flag - a combination of open flags from 'os' package. 26 | // perm - object's permission bits. 27 | // l - a locker, associated with the shared resource. 28 | func NewCond(name string, flag int, perm os.FileMode, l IPCLocker) (*Cond, error) { 29 | c, err := newCond(name, flag, perm, l) 30 | if err != nil { 31 | return nil, err 32 | } 33 | return (*Cond)(c), nil 34 | } 35 | 36 | // Signal wakes one waiter. 37 | func (c *Cond) Signal() { 38 | (*cond)(c).signal() 39 | } 40 | 41 | // Broadcast wakes all waiters. 42 | func (c *Cond) Broadcast() { 43 | (*cond)(c).broadcast() 44 | } 45 | 46 | // Wait waits for the condvar to be signaled. 47 | func (c *Cond) Wait() { 48 | (*cond)(c).wait() 49 | } 50 | 51 | // WaitTimeout waits for the condvar to be signaled for not longer, than timeout. 52 | func (c *Cond) WaitTimeout(timeout time.Duration) bool { 53 | return (*cond)(c).waitTimeout(timeout) 54 | } 55 | 56 | // Close releases resources of the cond's shared state. 57 | func (c *Cond) Close() error { 58 | return (*cond)(c).close() 59 | } 60 | 61 | // Destroy permanently removes condvar. 62 | func (c *Cond) Destroy() error { 63 | return (*cond)(c).destroy() 64 | } 65 | 66 | // DestroyCond permanently removes condvar with the given name. 67 | func DestroyCond(name string) error { 68 | return destroyCond(name) 69 | } 70 | -------------------------------------------------------------------------------- /sync/futex_freebsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "os" 7 | "time" 8 | "unsafe" 9 | 10 | "bitbucket.org/avd/go-ipc/internal/allocator" 11 | "bitbucket.org/avd/go-ipc/internal/common" 12 | 13 | "golang.org/x/sys/unix" 14 | ) 15 | 16 | const ( 17 | cUMTX_OP_WAIT_UINT = 0xb 18 | cUMTX_OP_WAIT_UINT_PRIVATE = 0xf 19 | cUMTX_OP_WAKE = 0x3 20 | cUMTX_OP_WAKE_PRIVATE = 0x10 21 | cUMTX_ABSTIME = 0x01 22 | ) 23 | 24 | // FutexWait checks if the the value equals futex's value. 25 | // If it doesn't, Wait returns EWOULDBLOCK. 26 | // Otherwise, it waits for the Wake call on the futex for not longer, than timeout. 27 | func FutexWait(addr unsafe.Pointer, value int32, timeout time.Duration, flags int32) error { 28 | return common.UninterruptedSyscallTimeout(func(tm time.Duration) error { 29 | var ptr unsafe.Pointer 30 | if flags&cUMTX_ABSTIME != 0 { 31 | ptr = unsafe.Pointer(common.AbsTimeoutToTimeSpec(tm)) 32 | } else { 33 | ptr = unsafe.Pointer(common.TimeoutToTimeSpec(tm)) 34 | } 35 | _, err := sys_umtx_op(addr, cUMTX_OP_WAIT_UINT|flags, value, nil, ptr) 36 | return err 37 | }, timeout) 38 | } 39 | 40 | // FutexWake wakes count threads waiting on the futex. 41 | // Returns the number of woken threads. 42 | func FutexWake(addr unsafe.Pointer, count int32, flags int32) (int, error) { 43 | var woken int32 44 | err := common.UninterruptedSyscall(func() error { 45 | var err error 46 | woken, err = sys_umtx_op(addr, cUMTX_OP_WAKE|flags, count, nil, nil) 47 | return err 48 | }) 49 | if err == nil { 50 | return int(woken), nil 51 | } 52 | return 0, err 53 | } 54 | 55 | func sys_umtx_op(addr unsafe.Pointer, mode int32, val int32, ptr2, ts unsafe.Pointer) (int32, error) { 56 | r1, _, err := unix.Syscall6(unix.SYS__UMTX_OP, 57 | uintptr(addr), 58 | uintptr(mode), 59 | uintptr(val), 60 | uintptr(ptr2), 61 | uintptr(ts), 62 | 0) 63 | allocator.Use(ptr2) 64 | allocator.Use(ts) 65 | if err != 0 { 66 | return 0, os.NewSyscallError("SYS__UMTX_OP", err) 67 | } 68 | return int32(r1), nil 69 | } 70 | -------------------------------------------------------------------------------- /shm/shared_memory_bsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build darwin freebsd 4 | 5 | package shm 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "syscall" 11 | "unsafe" 12 | 13 | "bitbucket.org/avd/go-ipc/internal/allocator" 14 | 15 | "golang.org/x/sys/unix" 16 | ) 17 | 18 | func doDestroyMemoryObject(path string) error { 19 | err := shm_unlink(path) 20 | if err != nil { 21 | if os.IsNotExist(err) { 22 | err = nil 23 | } 24 | } 25 | return err 26 | } 27 | 28 | func shmName(name string) (string, error) { 29 | const maxNameLen = 30 30 | // workaround from http://www.opensource.apple.com/source/Libc/Libc-320/sys/shm_open.c 31 | if isDarwin { 32 | newName := fmt.Sprintf("%s\t%d", name, unix.Geteuid()) 33 | if len(newName) < maxNameLen { 34 | name = newName 35 | } 36 | } 37 | return "/" + name, nil 38 | } 39 | 40 | func shmOpen(path string, flag int, perm os.FileMode) (*os.File, error) { 41 | flag |= unix.O_CLOEXEC 42 | fd, err := shm_open(path, flag, int(perm)) 43 | if err != nil { 44 | return nil, err 45 | } 46 | return os.NewFile(fd, path), nil 47 | } 48 | 49 | // syscalls 50 | 51 | func shm_open(name string, flags, mode int) (uintptr, error) { 52 | nameBytes, err := unix.BytePtrFromString(name) 53 | if err != nil { 54 | return 0, err 55 | } 56 | bytes := unsafe.Pointer(nameBytes) 57 | fd, _, err := unix.Syscall(unix.SYS_SHM_OPEN, uintptr(bytes), uintptr(flags), uintptr(mode)) 58 | allocator.Use(bytes) 59 | if err != syscall.Errno(0) { 60 | if err == unix.ENOENT || err == unix.EEXIST { 61 | return 0, &os.PathError{Path: name, Op: "shm_open", Err: err} 62 | } 63 | return 0, err 64 | } 65 | return fd, nil 66 | } 67 | 68 | func shm_unlink(name string) error { 69 | nameBytes, err := unix.BytePtrFromString(name) 70 | if err != nil { 71 | return err 72 | } 73 | bytes := unsafe.Pointer(nameBytes) 74 | _, _, err = unix.Syscall(unix.SYS_SHM_UNLINK, uintptr(bytes), uintptr(0), uintptr(0)) 75 | allocator.Use(bytes) 76 | if err != syscall.Errno(0) { 77 | if err == unix.ENOENT { 78 | return &os.PathError{Path: name, Op: "shm_unlink", Err: err} 79 | } 80 | return err 81 | } 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /sync/event_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "os" 7 | "time" 8 | 9 | "github.com/pkg/errors" 10 | "golang.org/x/sys/windows" 11 | ) 12 | 13 | // WindowsEvent gives access to system event object. 14 | type WindowsEvent struct { 15 | handle windows.Handle 16 | } 17 | 18 | // NewWindowsEvent returns new WindowsEvent. 19 | // name - object name. 20 | // flag - flag is a combination of open flags from 'os' package. 21 | // perm - object's permission bits. 22 | // initial - if truem the event will be set after creation. 23 | func NewWindowsEvent(name string, flag int, perm os.FileMode, initial bool) (*WindowsEvent, error) { 24 | if err := ensureOpenFlags(flag); err != nil { 25 | return nil, err 26 | } 27 | var init int 28 | if initial { 29 | init = 1 30 | } 31 | handle, err := openOrCreateEvent(name, flag, init) 32 | if err != nil { 33 | return nil, errors.Wrap(err, "failed to open/create event") 34 | } 35 | return &WindowsEvent{handle: handle}, nil 36 | } 37 | 38 | // Set sets the specified event object to the signaled state. 39 | func (e *WindowsEvent) Set() { 40 | if err := windows.SetEvent(e.handle); err != nil { 41 | panic("failed to set an event: " + err.Error()) 42 | } 43 | } 44 | 45 | // Wait waits for the event to be signaled. 46 | func (e *WindowsEvent) Wait() { 47 | e.WaitTimeout(-1) 48 | } 49 | 50 | // WaitTimeout waits until the event is signaled or the timeout elapses. 51 | func (e *WindowsEvent) WaitTimeout(timeout time.Duration) bool { 52 | waitMillis := uint32(windows.INFINITE) 53 | if timeout >= 0 { 54 | waitMillis = uint32(timeout.Nanoseconds() / 1e6) 55 | } 56 | ev, err := windows.WaitForSingleObject(e.handle, waitMillis) 57 | switch ev { 58 | case windows.WAIT_OBJECT_0: 59 | return true 60 | case uint32(windows.WAIT_TIMEOUT): 61 | return false 62 | default: 63 | if err != nil { 64 | panic(err) 65 | } else { 66 | panic(errors.Errorf("invalid wait state for an event: %d", ev)) 67 | } 68 | } 69 | } 70 | 71 | // Close closes the event. 72 | func (e *WindowsEvent) Close() error { 73 | if e.handle == windows.InvalidHandle { 74 | return nil 75 | } 76 | err := windows.CloseHandle(e.handle) 77 | e.handle = windows.InvalidHandle 78 | return err 79 | } 80 | -------------------------------------------------------------------------------- /mq/mq_sysv_sys.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build linux,amd64 darwin freebsd 4 | 5 | package mq 6 | 7 | import ( 8 | "os" 9 | "syscall" 10 | "unsafe" 11 | 12 | "bitbucket.org/avd/go-ipc/internal/allocator" 13 | "bitbucket.org/avd/go-ipc/internal/common" 14 | 15 | "golang.org/x/sys/unix" 16 | ) 17 | 18 | var ( 19 | sysMsgGet uintptr 20 | sysMsgSnd uintptr 21 | sysMsgRcv uintptr 22 | sysMsgCtl uintptr 23 | ) 24 | 25 | func msgget(k common.Key, flags int) (int, error) { 26 | id, _, err := unix.Syscall(sysMsgGet, uintptr(k), uintptr(flags), 0) 27 | if err != syscall.Errno(0) { 28 | if err == unix.EEXIST || err == unix.ENOENT { 29 | return 0, &os.PathError{Op: "MSGGET", Path: "", Err: err} 30 | } 31 | return 0, os.NewSyscallError("MSGGET", err) 32 | } 33 | return int(id), nil 34 | } 35 | 36 | func msgsnd(id int, typ int, data []byte, flags int) error { 37 | messageLen := typeDataSize + len(data) 38 | message := make([]byte, messageLen) 39 | rawData := allocator.ByteSliceData(message) 40 | *(*int)(rawData) = typ 41 | copy(message[typeDataSize:], data) 42 | _, _, err := unix.Syscall6(sysMsgSnd, 43 | uintptr(id), 44 | uintptr(rawData), 45 | uintptr(len(data)), 46 | uintptr(flags), 47 | 0, 48 | 0) 49 | allocator.Use(rawData) 50 | if err != syscall.Errno(0) { 51 | return os.NewSyscallError("MSGSND", err) 52 | } 53 | return nil 54 | } 55 | 56 | func msgrcv(id int, data []byte, typ int, flags int) (int, error) { 57 | messageLen := typeDataSize + len(data) 58 | message := make([]byte, messageLen) 59 | rawData := allocator.ByteSliceData(message) 60 | len, _, err := unix.Syscall6(sysMsgRcv, 61 | uintptr(id), 62 | uintptr(rawData), 63 | uintptr(len(data)), 64 | uintptr(typ), 65 | uintptr(flags), 66 | 0) 67 | allocator.Use(rawData) 68 | copy(data, message[typeDataSize:]) 69 | if err != syscall.Errno(0) { 70 | return 0, os.NewSyscallError("MSGRCV", err) 71 | } 72 | return int(len), nil 73 | } 74 | 75 | func msgctl(id, cmd int, buf *msqidDs) error { 76 | pBuf := unsafe.Pointer(buf) 77 | _, _, err := unix.Syscall(sysMsgCtl, uintptr(id), uintptr(cmd), uintptr(pBuf)) 78 | allocator.Use(pBuf) 79 | if err != syscall.Errno(0) { 80 | return os.NewSyscallError("MSGCTL", err) 81 | } 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /mq/mq_sys_sysv_linux_386.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build linux,386 4 | 5 | package mq 6 | 7 | import ( 8 | "os" 9 | "syscall" 10 | "unsafe" 11 | 12 | "bitbucket.org/avd/go-ipc/internal/allocator" 13 | "bitbucket.org/avd/go-ipc/internal/common" 14 | 15 | "golang.org/x/sys/unix" 16 | ) 17 | 18 | const ( 19 | cMSGSND = 11 20 | cMSGRCV = 12 21 | cMSGGET = 13 22 | cMSGCTL = 14 23 | ) 24 | 25 | func msgget(k common.Key, flags int) (int, error) { 26 | id, _, err := unix.Syscall6(unix.SYS_IPC, uintptr(cMSGGET), uintptr(k), uintptr(flags), 0, 0, 0) 27 | if err != syscall.Errno(0) { 28 | if err == unix.EEXIST || err == unix.ENOENT { 29 | return 0, &os.PathError{Op: "MSGGET", Path: "", Err: err} 30 | } 31 | return 0, os.NewSyscallError("MSGGET", err) 32 | } 33 | return int(id), nil 34 | } 35 | 36 | func msgsnd(id int, typ int, data []byte, flags int) error { 37 | messageLen := typeDataSize + len(data) 38 | message := make([]byte, messageLen) 39 | rawData := allocator.ByteSliceData(message) 40 | *(*int)(unsafe.Pointer(rawData)) = typ 41 | copy(message[typeDataSize:], data) 42 | _, _, err := unix.Syscall6(unix.SYS_IPC, 43 | uintptr(cMSGSND), 44 | uintptr(id), 45 | uintptr(len(data)), 46 | uintptr(flags), 47 | uintptr(rawData), 48 | 0) 49 | allocator.Use(rawData) 50 | if err != syscall.Errno(0) { 51 | return os.NewSyscallError("MSGSND", err) 52 | } 53 | return nil 54 | } 55 | 56 | func msgrcv(id int, data []byte, typ int, flags int) (int, error) { 57 | messageLen := typeDataSize + len(data) 58 | message := make([]byte, messageLen) 59 | rawData := allocator.ByteSliceData(message) 60 | len, _, err := unix.Syscall6(unix.SYS_IPC, 61 | uintptr(cMSGRCV|(1<<16)), 62 | uintptr(id), 63 | uintptr(len(data)), 64 | uintptr(flags), 65 | uintptr(rawData), 66 | uintptr(typ)) 67 | allocator.Use(rawData) 68 | copy(data, message[typeDataSize:]) 69 | if err != syscall.Errno(0) { 70 | return 0, os.NewSyscallError("MSGRCV", err) 71 | } 72 | return int(len), nil 73 | } 74 | 75 | func msgctl(id int, cmd int, buf *msqidDs) error { 76 | _, _, err := unix.Syscall(unix.SYS_IPC, uintptr(cMSGCTL), uintptr(id), uintptr(cmd)) 77 | if err != syscall.Errno(0) { 78 | return os.NewSyscallError("MSGCTL", err) 79 | } 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /sync/sema_timed_bsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build darwin freebsd 4 | 5 | package sync 6 | 7 | import ( 8 | "os" 9 | "os/signal" 10 | "runtime" 11 | "sync/atomic" 12 | "time" 13 | 14 | "bitbucket.org/avd/go-ipc/internal/common" 15 | 16 | "github.com/pkg/errors" 17 | "golang.org/x/sys/unix" 18 | ) 19 | 20 | // This is the emulation of semtimedop. 21 | // As darwin/bsd don't have semtimedop, we call semop 22 | // waiting for timeout to elapse in another goroutine. 23 | // After that, it sends SIGUSR2 to the blocked goroutie, 24 | // forcing it to interrupt the syscall with EINTR. 25 | // This, however, has some side effects: 26 | // - it locks the thread to get valid id. 27 | // - SIGUSR2 won't be ignored for the calling thread, if it was before. 28 | // The code uses the same idea, as used here: 29 | // https://github.com/attie/libxbee3/blob/master/xsys_darwin/sem_timedwait.c 30 | 31 | type threadInterrupter struct { 32 | state int32 33 | } 34 | 35 | func (ti *threadInterrupter) start(timeout time.Duration) error { 36 | // first, get thread id. goroutine must be locked on the thread. 37 | tid, err := gettid() 38 | if err != nil { 39 | return errors.Wrap(err, "failed to get thread id") 40 | } 41 | // then, restore SIGUSR2 handler if it was ignored before. 42 | // we don't know, if it was really igored, so we do it unconditionally. 43 | // side effect: SIGUSR2 won't be ignored again after the operation is complete. 44 | signal.Notify(make(chan os.Signal, 1), unix.SIGUSR2) 45 | signal.Reset(unix.SIGUSR2) 46 | go func() { 47 | time.Sleep(timeout) 48 | if atomic.LoadInt32(&ti.state) == 0 { 49 | killThread(tid) 50 | } 51 | }() 52 | return nil 53 | } 54 | 55 | func (ti *threadInterrupter) done() { 56 | atomic.StoreInt32(&ti.state, 1) 57 | } 58 | 59 | func doSemaTimedWait(id int, timeout time.Duration) bool { 60 | runtime.LockOSThread() 61 | defer runtime.UnlockOSThread() 62 | ti := threadInterrupter{} 63 | b := sembuf{semnum: 0, semop: int16(-1), semflg: 0} 64 | if err := ti.start(timeout); err != nil { 65 | panic(errors.Wrap(err, "failed to setup timeout")) 66 | } 67 | err := semop(id, []sembuf{b}) 68 | ti.done() 69 | if err == nil { 70 | return true 71 | } 72 | if common.IsInterruptedSyscallErr(err) { 73 | return false 74 | } 75 | panic(err) 76 | } 77 | -------------------------------------------------------------------------------- /shm/shared_memory_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package shm 4 | 5 | import ( 6 | "os" 7 | "path/filepath" 8 | "runtime" 9 | 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | // Shared memory on Windows is emulated via regular files 14 | // like it is done in boost c++ library. 15 | type memoryObject struct { 16 | file *os.File 17 | } 18 | 19 | func newMemoryObject(name string, flag int, perm os.FileMode) (impl *memoryObject, err error) { 20 | path, err := shmName(name) 21 | if err != nil { 22 | return nil, errors.Wrap(err, "shm name failed") 23 | } 24 | file, err := os.OpenFile(path, flag, perm) 25 | if err != nil { 26 | return nil, errors.Wrap(err, "open file failed") 27 | } 28 | return &memoryObject{file}, nil 29 | } 30 | 31 | func (obj *memoryObject) Destroy() error { 32 | if int(obj.Fd()) >= 0 { 33 | if err := obj.Close(); err != nil { 34 | return errors.Wrap(err, "close file failed") 35 | } 36 | } 37 | return DestroyMemoryObject(obj.Name()) 38 | } 39 | 40 | func (obj *memoryObject) Name() string { 41 | return filepath.Base(obj.file.Name()) 42 | } 43 | 44 | func (obj *memoryObject) Close() error { 45 | runtime.SetFinalizer(obj, nil) 46 | return obj.file.Close() 47 | } 48 | 49 | func (obj *memoryObject) Truncate(size int64) error { 50 | return obj.file.Truncate(size) 51 | } 52 | 53 | func (obj *memoryObject) Size() int64 { 54 | fileInfo, err := obj.file.Stat() 55 | if err != nil { 56 | return 0 57 | } 58 | return fileInfo.Size() 59 | } 60 | 61 | func (obj *memoryObject) Fd() uintptr { 62 | return obj.file.Fd() 63 | } 64 | 65 | func destroyMemoryObject(name string) error { 66 | path, err := shmName(name) 67 | if err != nil { 68 | return errors.Wrap(err, "shm name failed") 69 | } 70 | if err = os.Remove(path); os.IsNotExist(err) { 71 | err = nil 72 | } else { 73 | err = errors.Wrap(err, "remove file failed") 74 | } 75 | return err 76 | } 77 | 78 | func shmName(name string) (string, error) { 79 | path, err := sharedDirName() 80 | if err != nil { 81 | return "", errors.Wrap(err, "failed to get tmp directory name") 82 | } 83 | return path + "/" + name, nil 84 | } 85 | 86 | func sharedDirName() (string, error) { 87 | rootPath := os.TempDir() + "/go-ipc" 88 | if err := os.Mkdir(rootPath, 0644); err != nil && !os.IsExist(err) { 89 | return "", err 90 | } 91 | return rootPath, nil 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-ipc: A library for inter-process communication written in pure Go. 2 | This package gives you access to os-native ipc mechanisms on Linux, OSX, FreeBSD, and Windows. 3 | 4 | [![CircleCI](https://circleci.com/bb/avd/go-ipc/tree/master.svg?style=svg)](https://circleci.com/bb/avd/go-ipc/tree/master) [![GoDoc](https://godoc.org/bitbucket.org/avd/go-ipc?status.svg)](https://godoc.org/bitbucket.org/avd/go-ipc) [![Go Report Card](https://goreportcard.com/badge/bitbucket.org/avd/go-ipc)](https://goreportcard.com/report/bitbucket.org/avd/go-ipc) 5 | 6 | 7 | * Pure Go implementation, no cgo is required. 8 | * Works on Linux, OSX, FreeBSD, and Windows (x86 or x86-64). 9 | * Support of the following mechanisms: 10 | - fifo (unix and windows pipes) 11 | - memory mapped files 12 | - shared memory 13 | - system message queues (Linux, FreeBSD, OSX) 14 | - cross-platform priority message queue 15 | - mutexes, rw mutexes 16 | - semaphores 17 | - events 18 | - conditional variables 19 | 20 | ## Install 21 | 1. Install Go 1.4 or higher. 22 | 2. Run 23 | ``` 24 | go get -u bitbucket.org/avd/go-ipc 25 | ``` 26 | 27 | ## System requirements 28 | 1. Linux, OSX, FreeBSD, and Windows (x86 or x86-64). 29 | 2. Go 1.4 or higher. 30 | 31 | ## Documentation 32 | Documentation can be found at [`godoc`](https://godoc.org/bitbucket.org/avd/go-ipc). 33 | 34 | ## Notes 35 | 36 | ## Build status 37 | This library is currently beta. The 'master' branch is not guaranteed to contain stable code, 38 | it is even not guaranteed, that it builds correctly on all platforms. The library uses 39 | [Semantic Versioning 2.0.0](http://semver.org/), so it is recommended to checkout the latest release. 40 | 41 | ## Contributing 42 | Any contributions are welcome. 43 | Feel free to: 44 | 45 | - create [`issues`](https://bitbucket.org/avd/go-ipc/issues/new) 46 | - open [`pull requests`](https://bitbucket.org/avd/go-ipc/pull-requests/new) 47 | 48 | Before opening a PR, be sure, that: 49 | 50 | - your PR has an issue associated with it. 51 | - your commit messages are adequate. 52 | - you added unit tests for your code. 53 | - you gofmt'ed the code. 54 | - you used [`gometalinter`](https://github.com/alecthomas/gometalinter) to check your code. 55 | 56 | PR's containing documentation improvements and tests are especially welcome. 57 | 58 | ## LICENSE 59 | 60 | This package is made available under an Apache License 2.0. See 61 | LICENSE and NOTICE. -------------------------------------------------------------------------------- /sync/event_sema.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build windows darwin 4 | 5 | package sync 6 | 7 | import ( 8 | "os" 9 | "time" 10 | 11 | "bitbucket.org/avd/go-ipc/internal/allocator" 12 | "bitbucket.org/avd/go-ipc/internal/helper" 13 | "bitbucket.org/avd/go-ipc/mmf" 14 | "bitbucket.org/avd/go-ipc/shm" 15 | "github.com/pkg/errors" 16 | ) 17 | 18 | type event struct { 19 | s *Semaphore 20 | region *mmf.MemoryRegion 21 | name string 22 | lwe *lwEvent 23 | } 24 | 25 | func newEvent(name string, flag int, perm os.FileMode, initial bool) (*event, error) { 26 | if err := ensureOpenFlags(flag); err != nil { 27 | return nil, err 28 | } 29 | 30 | region, created, err := helper.CreateWritableRegion(eventName(name), flag, perm, lweStateSize) 31 | if err != nil { 32 | return nil, errors.Wrap(err, "failed to create shared state") 33 | } 34 | s, err := NewSemaphore(name, flag, perm, 0) 35 | if err != nil { 36 | region.Close() 37 | if created { 38 | shm.DestroyMemoryObject(mutexSharedStateName(name, "s")) 39 | } 40 | return nil, errors.Wrap(err, "failed to create a semaphore") 41 | } 42 | result := &event{ 43 | lwe: newLightweightEvent(allocator.ByteSliceData(region.Data()), newSemaWaiter(s)), 44 | name: name, 45 | region: region, 46 | s: s, 47 | } 48 | if created { 49 | result.lwe.init(initial) 50 | } 51 | return result, nil 52 | } 53 | 54 | func (e *event) set() { 55 | e.lwe.set() 56 | } 57 | 58 | func (e *event) wait() { 59 | e.waitTimeout(-1) 60 | } 61 | 62 | func (e *event) waitTimeout(timeout time.Duration) bool { 63 | return e.lwe.waitTimeout(timeout) 64 | } 65 | 66 | func (e *event) close() error { 67 | e1, e2 := e.s.Close(), e.region.Close() 68 | if e1 != nil { 69 | return errors.Wrap(e1, "failed to close sema") 70 | } 71 | if e2 != nil { 72 | return errors.Wrap(e2, "failed to close shared state") 73 | } 74 | return nil 75 | } 76 | 77 | func (e *event) destroy() error { 78 | if err := e.close(); err != nil { 79 | return errors.Wrap(err, "failed to close the event") 80 | } 81 | return destroyEvent(e.name) 82 | } 83 | 84 | func destroyEvent(name string) error { 85 | e1, e2 := shm.DestroyMemoryObject(eventName(name)), destroySemaphore(name) 86 | if e1 != nil { 87 | return errors.Wrap(e1, "failed to destroy memory object") 88 | } 89 | if e2 != nil { 90 | return errors.Wrap(e2, "failed to destroy semaphore") 91 | } 92 | return nil 93 | } 94 | -------------------------------------------------------------------------------- /sync/event_spin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build ignore 4 | 5 | package sync 6 | 7 | import ( 8 | "os" 9 | "runtime" 10 | "sync/atomic" 11 | "time" 12 | 13 | "bitbucket.org/avd/go-ipc/internal/allocator" 14 | "bitbucket.org/avd/go-ipc/internal/helper" 15 | "bitbucket.org/avd/go-ipc/mmf" 16 | "bitbucket.org/avd/go-ipc/shm" 17 | "github.com/pkg/errors" 18 | ) 19 | 20 | type event struct { 21 | name string 22 | region *mmf.MemoryRegion 23 | waiter *uint32 24 | } 25 | 26 | func newEvent(name string, flag int, perm os.FileMode, initial bool) (*event, error) { 27 | if err := ensureOpenFlags(flag); err != nil { 28 | return nil, err 29 | } 30 | 31 | region, created, err := helper.CreateWritableRegion(eventName(name), flag, perm, 4) 32 | if err != nil { 33 | return nil, errors.Wrap(err, "failed to create shared state") 34 | } 35 | result := &event{ 36 | waiter: (*uint32)(allocator.ByteSliceData(region.Data())), 37 | name: name, 38 | region: region, 39 | } 40 | 41 | if created && initial { 42 | *result.waiter = 1 43 | } 44 | return result, nil 45 | } 46 | 47 | func (e *event) set() { 48 | atomic.StoreUint32(e.waiter, 1) 49 | } 50 | 51 | func (e *event) wait() { 52 | for i := uint64(0); !atomic.CompareAndSwapUint32(e.waiter, 1, 0); i++ { 53 | if i%1000 == 0 { 54 | runtime.Gosched() 55 | } 56 | } 57 | } 58 | 59 | func (e *event) waitTimeout(timeout time.Duration) bool { 60 | var attempt uint64 61 | start := time.Now() 62 | for !atomic.CompareAndSwapUint32(e.waiter, 1, 0) { 63 | if attempt%1000 == 0 { // do not call time.Since too often. 64 | if timeout >= 0 && time.Since(start) >= timeout { 65 | return false 66 | } 67 | runtime.Gosched() 68 | } 69 | attempt++ 70 | } 71 | return true 72 | } 73 | 74 | func (e *event) close() error { 75 | if e.region == nil { 76 | return nil 77 | } 78 | err := e.region.Close() 79 | e.region = nil 80 | e.waiter = nil 81 | return err 82 | } 83 | 84 | func (e *event) destroy() error { 85 | if e.region == nil { 86 | return nil 87 | } 88 | if err := e.close(); err != nil { 89 | return errors.Wrap(err, "failed to close shm region") 90 | } 91 | return destroyEvent(e.name) 92 | } 93 | 94 | func destroyEvent(name string) error { 95 | err := shm.DestroyMemoryObject(eventName(name)) 96 | if err != nil { 97 | return errors.Wrap(err, "failed to destroy memory object") 98 | } 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /fifo/fifo_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build darwin freebsd linux 4 | 5 | package fifo 6 | 7 | import ( 8 | "os" 9 | 10 | "bitbucket.org/avd/go-ipc/internal/common" 11 | 12 | "github.com/pkg/errors" 13 | "golang.org/x/sys/unix" 14 | ) 15 | 16 | // UnixFifo is a first-in-first-out unix ipc mechanism. 17 | type UnixFifo struct { 18 | file *os.File 19 | } 20 | 21 | // NewUnixFifo creates a new unix FIFO. 22 | // name - object name. 23 | // flag - flag is a combination of open flags from 'os' package. 24 | // perm = object permissions. 25 | func NewUnixFifo(name string, flag int, perm os.FileMode) (*UnixFifo, error) { 26 | if flag&os.O_RDWR != 0 { 27 | // open man says "The result is undefined if this flag is applied to a FIFO." 28 | // so, we don't allow it and return an error 29 | return nil, errors.Errorf("O_RDWR flag cannot be used for FIFO") 30 | } 31 | path := fifoPath(name) 32 | var file *os.File 33 | creator := func(create bool) error { 34 | var err error 35 | if create { 36 | err = unix.Mkfifo(path, uint32(perm)) 37 | } 38 | if err == nil { 39 | file, err = os.OpenFile(path, common.FlagsForAccess(flag), perm) 40 | } 41 | return err 42 | } 43 | if _, err := common.OpenOrCreate(creator, flag); err != nil { 44 | return nil, errors.Wrap(err, "open/create fifo failed") 45 | } 46 | return &UnixFifo{file: file}, nil 47 | } 48 | 49 | // Read reads from the given FIFO. it must be opened for reading. 50 | func (f *UnixFifo) Read(b []byte) (n int, err error) { 51 | return f.file.Read(b) 52 | } 53 | 54 | // Write writes to the given FIFO. it must be opened for writing. 55 | func (f *UnixFifo) Write(b []byte) (n int, err error) { 56 | return f.file.Write(b) 57 | } 58 | 59 | // Close closes the object. 60 | func (f *UnixFifo) Close() error { 61 | return f.file.Close() 62 | } 63 | 64 | // Destroy permanently removes the FIFO, closing it first. 65 | func (f *UnixFifo) Destroy() error { 66 | var err error 67 | err = f.file.Close() 68 | if err != nil { 69 | return errors.Wrap(err, "close failed") 70 | } 71 | if err = os.Remove(f.file.Name()); err != nil { 72 | if !os.IsNotExist(err) { 73 | return errors.Wrap(err, "remove failed") 74 | } 75 | } 76 | return nil 77 | } 78 | 79 | // DestroyUnixFIFO permanently removes the FIFO. 80 | func DestroyUnixFIFO(name string) error { 81 | err := os.Remove(fifoPath(name)) 82 | if os.IsNotExist(err) { 83 | return nil 84 | } 85 | return errors.Wrap(err, "remove failed") 86 | } 87 | 88 | func fifoPath(name string) string { 89 | return "/tmp/" + name 90 | } 91 | -------------------------------------------------------------------------------- /sync/lwmutex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "sync/atomic" 7 | "time" 8 | "unsafe" 9 | 10 | "bitbucket.org/avd/go-ipc/internal/common" 11 | ) 12 | 13 | const ( 14 | lwmStateSize = 4 15 | 16 | lwmSpinCount = 100 17 | lwmUnlocked = int32(0) 18 | lwmLockedNoWaiters = int32(1) 19 | lwmLockedHaveWaiters = int32(2) 20 | ) 21 | 22 | // lwMutex is a lightweight mutex implementation operating on a uint32 memory cell. 23 | // it tries to minimize amount of syscalls needed to do locking. 24 | // actual sleeping must be implemented by a waitWaker object. 25 | type lwMutex struct { 26 | state *int32 27 | ww waitWaker 28 | } 29 | 30 | func newLightweightMutex(state unsafe.Pointer, ww waitWaker) *lwMutex { 31 | return &lwMutex{state: (*int32)(state), ww: ww} 32 | } 33 | 34 | // init writes initial value into mutex's memory location. 35 | func (lwm *lwMutex) init() { 36 | *lwm.state = lwmUnlocked 37 | } 38 | 39 | func (lwm *lwMutex) lock() { 40 | if err := lwm.doLock(-1); err != nil { 41 | panic(err) 42 | } 43 | } 44 | 45 | func (lwm *lwMutex) tryLock() bool { 46 | return atomic.CompareAndSwapInt32(lwm.state, lwmUnlocked, lwmLockedNoWaiters) 47 | } 48 | 49 | func (lwm *lwMutex) lockTimeout(timeout time.Duration) bool { 50 | err := lwm.doLock(timeout) 51 | if err == nil { 52 | return true 53 | } 54 | if common.IsTimeoutErr(err) { 55 | return false 56 | } 57 | panic(err) 58 | } 59 | 60 | func (lwm *lwMutex) doLock(timeout time.Duration) error { 61 | for i := 0; i < lwmSpinCount; i++ { 62 | if lwm.tryLock() { 63 | return nil 64 | } 65 | } 66 | old := atomic.LoadInt32(lwm.state) 67 | if old != lwmLockedHaveWaiters { 68 | old = atomic.SwapInt32(lwm.state, lwmLockedHaveWaiters) 69 | } 70 | for old != lwmUnlocked { 71 | if err := lwm.ww.wait(lwmLockedHaveWaiters, timeout); err != nil { 72 | return err 73 | } 74 | old = atomic.SwapInt32(lwm.state, lwmLockedHaveWaiters) 75 | } 76 | return nil 77 | } 78 | 79 | func (lwm *lwMutex) unlock() { 80 | if old := atomic.LoadInt32(lwm.state); old == lwmLockedHaveWaiters { 81 | *lwm.state = lwmUnlocked 82 | } else { 83 | if old == lwmUnlocked { 84 | panic("unlock of unlocked mutex") 85 | } 86 | if atomic.SwapInt32(lwm.state, lwmUnlocked) == lwmLockedNoWaiters { 87 | return 88 | } 89 | } 90 | for i := 0; i < lwmSpinCount; i++ { 91 | if *lwm.state != lwmUnlocked { 92 | if atomic.CompareAndSwapInt32(lwm.state, lwmLockedNoWaiters, lwmLockedHaveWaiters) { 93 | return 94 | } 95 | } 96 | } 97 | lwm.ww.wake(1) 98 | } 99 | -------------------------------------------------------------------------------- /shm/shared_memory_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build darwin freebsd linux 4 | 5 | package shm 6 | 7 | import ( 8 | "os" 9 | "path/filepath" 10 | "runtime" 11 | "strings" 12 | 13 | "github.com/pkg/errors" 14 | ) 15 | 16 | const ( 17 | isDarwin = runtime.GOOS == "darwin" 18 | ) 19 | 20 | type memoryObject struct { 21 | file *os.File 22 | } 23 | 24 | func newMemoryObject(name string, flag int, perm os.FileMode) (*memoryObject, error) { 25 | path, err := shmName(name) 26 | if err != nil { 27 | return nil, errors.Wrap(err, "shm name failed") 28 | } 29 | file, err := shmOpen(path, flag, perm) 30 | if err != nil { 31 | return nil, errors.Wrap(err, "shm open failed") 32 | } 33 | return &memoryObject{file: file}, nil 34 | } 35 | 36 | func (obj *memoryObject) Destroy() error { 37 | if int(obj.Fd()) >= 0 { 38 | if err := obj.Close(); err != nil { 39 | return errors.Wrap(err, "close failed") 40 | } 41 | } 42 | if err := doDestroyMemoryObject(obj.file.Name()); err != nil { 43 | return errors.Wrap(err, "unable to destroy memory object") 44 | } 45 | return nil 46 | } 47 | 48 | func (obj *memoryObject) Name() string { 49 | result := filepath.Base(obj.file.Name()) 50 | // on darwin we do this trick due to 51 | // http://www.opensource.apple.com/source/Libc/Libc-320/sys/shm_open.c 52 | if isDarwin { 53 | result = result[:strings.LastIndex(result, "\t")] 54 | } 55 | return result 56 | } 57 | 58 | func (obj *memoryObject) Close() error { 59 | fdBeforeClose := obj.Fd() 60 | err := obj.file.Close() 61 | if err == nil { 62 | return nil 63 | } 64 | if isDarwin { 65 | // we're closing the file for the first time, and 66 | // we haven't truncated the file and it hasn't been closed 67 | if obj.Size() == 0 && int(fdBeforeClose) >= 0 { 68 | return nil 69 | } 70 | } 71 | return err 72 | } 73 | 74 | func (obj *memoryObject) Truncate(size int64) error { 75 | return obj.file.Truncate(size) 76 | } 77 | 78 | func (obj *memoryObject) Size() int64 { 79 | fileInfo, err := obj.file.Stat() 80 | if err != nil { 81 | return 0 82 | } 83 | return fileInfo.Size() 84 | } 85 | 86 | func (obj *memoryObject) Fd() uintptr { 87 | return obj.file.Fd() 88 | } 89 | 90 | func destroyMemoryObject(name string) error { 91 | path, err := shmName(name) 92 | if err != nil { 93 | return errors.Wrap(err, "shm name failed") 94 | } 95 | if err = doDestroyMemoryObject(path); err != nil { 96 | err = errors.Wrapf(err, "failed to destroy shm object %q", path) 97 | } 98 | return err 99 | } 100 | -------------------------------------------------------------------------------- /sync/lwevent.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "math" 7 | "sync/atomic" 8 | "time" 9 | "unsafe" 10 | 11 | "bitbucket.org/avd/go-ipc/internal/common" 12 | ) 13 | 14 | const ( 15 | lweStateSize = 4 16 | ) 17 | 18 | // lwEvent is a lightweight event implementation operating on a uint32 memory cell. 19 | // it tries to minimize amount of syscalls. 20 | // actual wait/wake must be implemented by a waitWaker object. 21 | // state is a shared variable, that contains event state: 22 | // the highest bit is a signal bit 23 | // all other bits define the number of waiters. 24 | type lwEvent struct { 25 | state *int32 26 | ww waitWaker 27 | } 28 | 29 | func newLightweightEvent(state unsafe.Pointer, ww waitWaker) *lwEvent { 30 | return &lwEvent{state: (*int32)(state), ww: ww} 31 | } 32 | 33 | func (e *lwEvent) init(set bool) { 34 | val := int32(0) 35 | if set { 36 | val = math.MinInt32 37 | } 38 | *e.state = val 39 | } 40 | 41 | func (e *lwEvent) set() { 42 | var old int32 43 | for { 44 | old = atomic.LoadInt32(e.state) 45 | if old < 0 { 46 | return 47 | } 48 | new := old | math.MinInt32 49 | if atomic.CompareAndSwapInt32(e.state, old, new) { 50 | break 51 | } 52 | } 53 | if old > 0 { 54 | e.ww.wake(1) 55 | } 56 | } 57 | 58 | func (e *lwEvent) obtainOrChange(inc int32) (new int32, obtained bool) { 59 | for { 60 | old := atomic.LoadInt32(e.state) 61 | new = old 62 | if old < 0 { // reset 'set' bit 63 | new = old & ^math.MinInt32 64 | } else { // change the value 65 | if inc == 0 { 66 | return 67 | } 68 | new = old + inc 69 | } 70 | if atomic.CompareAndSwapInt32(e.state, old, new) { 71 | if old < 0 { // bit was set and we reset it. success. otherwise, we changed the value. 72 | obtained = true 73 | } 74 | return 75 | } 76 | } 77 | } 78 | 79 | func (e *lwEvent) waitTimeout(timeout time.Duration) bool { 80 | // first, we are trying to catch the event, or add us as a waiter. 81 | new, obtained := e.obtainOrChange(1) 82 | if obtained { 83 | return true 84 | } 85 | // in the loop we wait for the value to change and then observe new value: 86 | // if it is still not set, wait again 87 | // otherwise, try to obtain the event. 88 | for { 89 | if err := e.ww.wait(new, timeout); err != nil { 90 | if common.IsTimeoutErr(err) { 91 | _, obtained = e.obtainOrChange(-1) 92 | return obtained 93 | } 94 | } 95 | new, obtained = e.obtainOrChange(0) 96 | if obtained { 97 | return true 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /sync/semaphore.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "os" 7 | "time" 8 | 9 | "bitbucket.org/avd/go-ipc/internal/common" 10 | ) 11 | 12 | const ( 13 | // CSemMaxVal is the maximum semaphore value, 14 | // which is guaranteed to be supported on all platforms. 15 | CSemMaxVal = 32767 16 | ) 17 | 18 | // Semaphore is a synchronization object with a resource counter, 19 | // which can be used to control access to a shared resource. 20 | // It provides access to actual OS semaphore primitive via: 21 | // CreateSemaprore on windows 22 | // semget on unix 23 | type Semaphore semaphore 24 | 25 | // NewSemaphore creates new semaphore with the given name. 26 | // name - object name. 27 | // flag - flag is a combination of open flags from 'os' package. 28 | // perm - object's permission bits. 29 | // initial - this value will be added to the semaphore's value, if it was created. 30 | func NewSemaphore(name string, flag int, perm os.FileMode, initial int) (*Semaphore, error) { 31 | result, err := newSemaphore(name, flag, perm, initial) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return (*Semaphore)(result), nil 36 | } 37 | 38 | // Signal increments the value of semaphore variable by 1, waking waiting process (if any). 39 | func (s *Semaphore) Signal(count int) { 40 | (*semaphore)(s).signal(count) 41 | } 42 | 43 | // Wait decrements the value of semaphore variable by -1, and blocks if the value becomes negative. 44 | func (s *Semaphore) Wait() { 45 | (*semaphore)(s).wait() 46 | } 47 | 48 | // Close closes the semaphore. 49 | func (s *Semaphore) Close() error { 50 | return (*semaphore)(s).close() 51 | } 52 | 53 | // WaitTimeout decrements the value of semaphore variable by 1. 54 | // If the value becomes negative, it waites for not longer than timeout. 55 | // On darwin and freebsd this func has some side effects, see sema_timed_bsd.go for details. 56 | func (s *Semaphore) WaitTimeout(timeout time.Duration) bool { 57 | return (*semaphore)(s).waitTimeout(timeout) 58 | } 59 | 60 | // DestroySemaphore removes the semaphore permanently. 61 | func DestroySemaphore(name string) error { 62 | return destroySemaphore(name) 63 | } 64 | 65 | type semaWaiter struct { 66 | s *Semaphore 67 | } 68 | 69 | func newSemaWaiter(s *Semaphore) *semaWaiter { 70 | return &semaWaiter{s: s} 71 | } 72 | 73 | func (sw *semaWaiter) wake(count int32) (int, error) { 74 | sw.s.Signal(int(count)) 75 | return int(count), nil 76 | } 77 | 78 | func (sw *semaWaiter) wait(unused int32, timeout time.Duration) error { 79 | if !sw.s.WaitTimeout(timeout) { 80 | return common.NewTimeoutError("SEMWAWIT") 81 | } 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /sync/futex_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "os" 7 | "time" 8 | "unsafe" 9 | 10 | "golang.org/x/sys/unix" 11 | 12 | "bitbucket.org/avd/go-ipc/internal/allocator" 13 | "bitbucket.org/avd/go-ipc/internal/common" 14 | ) 15 | 16 | const ( 17 | cFUTEX_WAIT = 0 18 | cFUTEX_WAKE = 1 19 | cFUTEX_REQUEUE = 3 20 | cFUTEX_CMP_REQUEUE = 4 21 | cFUTEX_WAKE_OP = 5 22 | 23 | // FUTEX_PRIVATE_FLAG is used to optimize futex usage for process-private futexes. 24 | FUTEX_PRIVATE_FLAG = 128 25 | // FUTEX_CLOCK_REALTIME is used to tell the kernel, that is must treat timeouts for 26 | // FUTEX_WAIT_BITSET, FUTEX_WAIT_REQUEUE_PI, and FUTEX_WAIT as an absolute time based on CLOCK_REALTIME 27 | FUTEX_CLOCK_REALTIME = 256 28 | ) 29 | 30 | var ( 31 | futexSyscallErr = os.NewSyscallError("FUTEX", unix.EWOULDBLOCK) 32 | ) 33 | 34 | // FutexWait checks if the the value equals futex's value. 35 | // If it doesn't, Wait returns EWOULDBLOCK. 36 | // Otherwise, it waits for the Wake call on the futex for not longer, than timeout. 37 | func FutexWait(addr unsafe.Pointer, value int32, timeout time.Duration, flags int32) error { 38 | return common.UninterruptedSyscallTimeout(func(tm time.Duration) error { 39 | var ptr unsafe.Pointer 40 | if flags&FUTEX_CLOCK_REALTIME != 0 { 41 | ptr = unsafe.Pointer(common.AbsTimeoutToTimeSpec(tm)) 42 | } else { 43 | ptr = unsafe.Pointer(common.TimeoutToTimeSpec(tm)) 44 | } 45 | _, err := sys_futex(addr, cFUTEX_WAIT|flags, value, ptr, nil, 0) 46 | return err 47 | }, timeout) 48 | } 49 | 50 | // FutexWake wakes count threads waiting on the futex. 51 | // Returns number of woken threads. 52 | func FutexWake(addr unsafe.Pointer, count int32, flags int32) (int, error) { 53 | var woken int32 54 | err := common.UninterruptedSyscall(func() error { 55 | var err error 56 | woken, err = sys_futex(addr, cFUTEX_WAKE|flags, count, nil, nil, 0) 57 | return err 58 | }) 59 | if err == nil { 60 | return int(woken), nil 61 | } 62 | return 0, err 63 | } 64 | 65 | func sys_futex(addr unsafe.Pointer, op int32, val int32, ts, addr2 unsafe.Pointer, val3 uint32) (int32, error) { 66 | r1, _, err := unix.Syscall6(unix.SYS_FUTEX, 67 | uintptr(addr), 68 | uintptr(op), 69 | uintptr(val), 70 | uintptr(ts), 71 | uintptr(addr2), 72 | uintptr(val3)) 73 | allocator.Use(addr) 74 | allocator.Use(addr2) 75 | switch err { 76 | case 0: 77 | return int32(r1), nil 78 | case unix.EWOULDBLOCK: // optimization: as EWOULDBLOCK can occur too often, do not allocate a syscall error each time. 79 | return 0, futexSyscallErr 80 | default: 81 | return 0, os.NewSyscallError("FUTEX", err) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /shm/shared_memory_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package shm 4 | 5 | import ( 6 | "os" 7 | 8 | "bitbucket.org/avd/go-ipc/mmf" 9 | ) 10 | 11 | func ExampleMemoryObject() { 12 | // cleanup previous objects 13 | DestroyMemoryObject("obj") 14 | // create new object and resize it. 15 | obj, err := NewMemoryObject("obj", os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666) 16 | if err != nil { 17 | panic("new") 18 | } 19 | if err := obj.Truncate(1024); err != nil { 20 | panic("truncate") 21 | } 22 | // create two regions for reading and writing. 23 | rwRegion, err := mmf.NewMemoryRegion(obj, mmf.MEM_READWRITE, 0, 1024) 24 | if err != nil { 25 | panic("new region") 26 | } 27 | defer rwRegion.Close() 28 | roRegion, err := mmf.NewMemoryRegion(obj, mmf.MEM_READ_ONLY, 0, 1024) 29 | if err != nil { 30 | panic("new region") 31 | } 32 | defer roRegion.Close() 33 | // copy some data to the first region and read it via the second one. 34 | data := []byte{1, 2, 3, 4, 5, 6, 7, 8} 35 | copy(rwRegion.Data(), data) 36 | for i, b := range data { 37 | if b != roRegion.Data()[i] { 38 | panic("bad data") 39 | } 40 | } 41 | } 42 | 43 | func ExampleMemoryObject_readWriter() { 44 | // this example shows how to use memory obects with memory region readers/writers. 45 | 46 | // cleanup previous objects 47 | DestroyMemoryObject("obj") 48 | // create new object and resize it. 49 | obj, err := NewMemoryObject("obj", os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666) 50 | if err != nil { 51 | panic("new") 52 | } 53 | if err := obj.Truncate(1024); err != nil { 54 | panic("truncate") 55 | } 56 | // create two regions for reading and writing. 57 | rwRegion, err := mmf.NewMemoryRegion(obj, mmf.MEM_READWRITE, 0, 1024) 58 | if err != nil { 59 | panic("new region") 60 | } 61 | defer rwRegion.Close() 62 | roRegion, err := mmf.NewMemoryRegion(obj, mmf.MEM_READ_ONLY, 0, 1024) 63 | if err != nil { 64 | panic("new region") 65 | } 66 | defer roRegion.Close() 67 | // for each region we create a reader and a writer, which is a better solution, than 68 | // using region.Data() bytes directly. 69 | writer := mmf.NewMemoryRegionWriter(rwRegion) 70 | reader := mmf.NewMemoryRegionReader(roRegion) 71 | // write data at the specified offset 72 | data := []byte{1, 2, 3, 4, 5, 6, 7, 8} 73 | written, err := writer.WriteAt(data, 128) 74 | if err != nil || written != len(data) { 75 | panic("write") 76 | } 77 | // read data at the same offset via another region. 78 | actual := make([]byte, len(data)) 79 | read, err := reader.ReadAt(actual, 128) 80 | if err != nil || read != len(data) { 81 | panic("read") 82 | } 83 | for i, b := range data { 84 | if b != actual[i] { 85 | panic("wrong data") 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /mq/internal/test/mq_helper_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "time" 9 | 10 | "bitbucket.org/avd/go-ipc/mq" 11 | ) 12 | 13 | func createMqWithType(name string, perm os.FileMode, typ, opt string) (mq.Messenger, error) { 14 | switch typ { 15 | case "default": 16 | return mq.New(name, os.O_RDWR, perm) 17 | case "sysv": 18 | return mq.CreateSystemVMessageQueue(name, os.O_RDWR, perm) 19 | case "fast": 20 | mqSize, msgSize := mq.DefaultLinuxMqMaxSize, mq.DefaultLinuxMqMessageSize 21 | if first, second, err := parseTwoInts(opt); err == nil { 22 | mqSize, msgSize = first, second 23 | } 24 | return mq.CreateFastMq(name, 0, perm, mqSize, msgSize) 25 | case "linux": 26 | mqSize, msgSize := mq.DefaultLinuxMqMaxSize, mq.DefaultLinuxMqMessageSize 27 | if first, second, err := parseTwoInts(opt); err == nil { 28 | mqSize, msgSize = first, second 29 | } 30 | return mq.CreateLinuxMessageQueue(name, os.O_RDWR, perm, mqSize, msgSize) 31 | default: 32 | return nil, fmt.Errorf("unknown mq type %q", typ) 33 | } 34 | } 35 | 36 | func openMqWithType(name string, flags int, typ string) (mq.Messenger, error) { 37 | switch typ { 38 | case "default": 39 | return mq.Open(name, flags) 40 | case "sysv": 41 | return mq.OpenSystemVMessageQueue(name, flags) 42 | case "fast": 43 | return mq.OpenFastMq(name, flags) 44 | case "linux": 45 | return mq.OpenLinuxMessageQueue(name, flags) 46 | default: 47 | return nil, fmt.Errorf("unknown mq type %q", typ) 48 | } 49 | } 50 | 51 | func destroyMqWithType(name, typ string) error { 52 | switch typ { 53 | case "default": 54 | return mq.Destroy(name) 55 | case "sysv": 56 | return mq.DestroySystemVMessageQueue(name) 57 | case "fast": 58 | return mq.DestroyFastMq(name) 59 | case "linux": 60 | return mq.DestroyLinuxMessageQueue(name) 61 | default: 62 | return fmt.Errorf("unknown mq type %q", typ) 63 | } 64 | } 65 | 66 | func notifywait(name string, timeout int, typ string) error { 67 | if typ != "linux" { 68 | return fmt.Errorf("notifywait is supported for 'linux' mq, not '%s'", typ) 69 | } 70 | mq, err := mq.OpenLinuxMessageQueue(name, os.O_RDWR) 71 | if err != nil { 72 | return err 73 | } 74 | defer mq.Close() 75 | notifyChan := make(chan int, 1) 76 | if err = mq.Notify(notifyChan); err != nil { 77 | return err 78 | } 79 | var timeChan <-chan time.Time 80 | if timeout > 0 { 81 | timeChan = time.After(time.Duration(timeout) * time.Millisecond) 82 | } 83 | select { 84 | case id := <-notifyChan: 85 | if id != mq.ID() { 86 | return fmt.Errorf("expected mq with id %q, got with %q", mq.ID(), id) 87 | } 88 | case <-timeChan: 89 | return fmt.Errorf("operation timeout") 90 | } 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /sync/cond_futex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | //+build freebsd linux 4 | 5 | package sync 6 | 7 | import ( 8 | "os" 9 | "time" 10 | 11 | "bitbucket.org/avd/go-ipc/internal/allocator" 12 | "bitbucket.org/avd/go-ipc/internal/common" 13 | "bitbucket.org/avd/go-ipc/internal/helper" 14 | "bitbucket.org/avd/go-ipc/mmf" 15 | "bitbucket.org/avd/go-ipc/shm" 16 | "github.com/pkg/errors" 17 | ) 18 | 19 | // cond is a futex-based convar. 20 | type cond struct { 21 | L IPCLocker 22 | name string 23 | region *mmf.MemoryRegion 24 | ftx *futex 25 | } 26 | 27 | func newCond(name string, flag int, perm os.FileMode, l IPCLocker) (*cond, error) { 28 | if err := ensureOpenFlags(flag); err != nil { 29 | return nil, err 30 | } 31 | 32 | region, _, err := helper.CreateWritableRegion(condSharedStateName(name), flag, perm, lwmStateSize) 33 | if err != nil { 34 | return nil, errors.Wrap(err, "failed to create shared state") 35 | } 36 | 37 | result := &cond{ 38 | L: l, 39 | name: name, 40 | ftx: &futex{(allocator.ByteSliceData(region.Data()))}, 41 | region: region, 42 | } 43 | 44 | return result, nil 45 | } 46 | 47 | func (c *cond) signal() { 48 | c.ftx.add(1) 49 | _, err := c.ftx.wake(1) 50 | if err != nil { 51 | panic(err) 52 | } 53 | } 54 | 55 | func (c *cond) broadcast() { 56 | c.ftx.add(1) 57 | _, err := c.ftx.wakeAll() 58 | if err != nil { 59 | panic(err) 60 | } 61 | } 62 | 63 | func (c *cond) wait() { 64 | seq := *c.ftx.addr() 65 | c.L.Unlock() 66 | if err := c.ftx.wait(seq, time.Duration(-1)); err != nil { 67 | panic(err) 68 | } 69 | c.L.Lock() 70 | } 71 | 72 | func (c *cond) waitTimeout(timeout time.Duration) bool { 73 | seq := *c.ftx.addr() 74 | var success bool 75 | c.L.Unlock() 76 | if err := c.ftx.wait(seq, timeout); err == nil { 77 | success = true 78 | } else if !common.IsTimeoutErr(err) { 79 | panic(err) 80 | } 81 | c.L.Lock() 82 | return success 83 | } 84 | 85 | func (c *cond) close() error { 86 | if err := c.region.Close(); err != nil { 87 | return errors.Wrap(err, "failed to close waiters list memory region") 88 | } 89 | return nil 90 | } 91 | 92 | func (c *cond) destroy() error { 93 | var result error 94 | if err := c.close(); err != nil { 95 | result = errors.Wrap(err, "destroy failed") 96 | } 97 | if err := shm.DestroyMemoryObject(condSharedStateName(c.name)); err != nil { 98 | result = errors.Wrap(err, "failed to close waiters list memory object") 99 | } 100 | return result 101 | } 102 | 103 | func destroyCond(name string) error { 104 | return shm.DestroyMemoryObject(condSharedStateName(name)) 105 | } 106 | 107 | func condSharedStateName(name string) string { 108 | return name + ".st" 109 | } 110 | -------------------------------------------------------------------------------- /mq/shared_heap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package mq 4 | 5 | import ( 6 | "container/heap" 7 | "errors" 8 | "unsafe" 9 | 10 | "bitbucket.org/avd/go-ipc/internal/allocator" 11 | "bitbucket.org/avd/go-ipc/internal/array" 12 | ) 13 | 14 | type message struct { 15 | prio int32 16 | data []byte 17 | } 18 | 19 | type sharedHeap struct { 20 | array *array.SharedArray 21 | } 22 | 23 | func newSharedHeap(raw unsafe.Pointer, maxQueueSize, maxMsgSize int) *sharedHeap { 24 | return &sharedHeap{ 25 | array: array.NewSharedArray(raw, maxQueueSize, maxMsgSize+4), 26 | } 27 | } 28 | 29 | func openSharedHeap(raw unsafe.Pointer) *sharedHeap { 30 | return &sharedHeap{ 31 | array: array.OpenSharedArray(raw), 32 | } 33 | } 34 | 35 | func (mq *sharedHeap) maxMsgSize() int { 36 | return mq.array.ElemSize() - 4 37 | } 38 | 39 | func (mq *sharedHeap) maxSize() int { 40 | return mq.array.Cap() 41 | } 42 | 43 | func (mq *sharedHeap) at(i int) message { 44 | data := mq.array.At(i) 45 | rawData := allocator.ByteSliceData(data) 46 | return message{prio: *(*int32)(rawData), data: data[4:]} 47 | } 48 | 49 | func (mq *sharedHeap) pushMessage(msg *message) { 50 | heap.Push(mq, msg) 51 | } 52 | 53 | func (mq *sharedHeap) popMessage(data []byte) (int, int, error) { 54 | msg := mq.at(0) 55 | if len(msg.data) > len(data) { 56 | return 0, 0, errors.New("the message is too long") 57 | } 58 | copy(data, msg.data) 59 | heap.Pop(mq) 60 | return len(msg.data), int(msg.prio), nil 61 | } 62 | 63 | func (mq *sharedHeap) safeLen() int { 64 | return mq.array.SafeLen() 65 | } 66 | 67 | // sort.Interface 68 | 69 | func (mq *sharedHeap) Len() int { 70 | return mq.array.Len() 71 | } 72 | 73 | func (mq *sharedHeap) Less(i, j int) bool { 74 | if i == j { 75 | return false 76 | } 77 | lhs, rhs := (*int32)(mq.array.AtPointer(i)), (*int32)(mq.array.AtPointer(j)) 78 | // inverse less logic, as we want max-heap. 79 | return *lhs > *rhs 80 | } 81 | 82 | func (mq *sharedHeap) Swap(i, j int) { 83 | if i != j { 84 | mq.array.Swap(i, j) 85 | } 86 | } 87 | 88 | // heap.Interface 89 | 90 | func (mq *sharedHeap) Push(x interface{}) { 91 | msg := x.(*message) 92 | prioData := allocator.ByteSliceFromUnsafePointer(unsafe.Pointer(&msg.prio), 4, 4) 93 | mq.array.PushBack(prioData, msg.data) 94 | } 95 | 96 | func (mq *sharedHeap) Pop() interface{} { 97 | mq.array.PopBack() 98 | return nil 99 | } 100 | 101 | func calcSharedHeapSize(maxQueueSize, maxMsgSize int) (int, error) { 102 | if maxQueueSize == 0 || maxMsgSize == 0 { 103 | return 0, errors.New("queue size cannot be zero") 104 | } 105 | return array.CalcSharedArraySize(maxQueueSize, maxMsgSize+4), nil 106 | } 107 | 108 | func minHeapSize() int { 109 | return array.CalcSharedArraySize(0, 0) 110 | } 111 | -------------------------------------------------------------------------------- /sync/internal/test/cond/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "os" 9 | "time" 10 | 11 | "bitbucket.org/avd/go-ipc/sync" 12 | ) 13 | 14 | var ( 15 | timeout = flag.Int("timeout", -1, "timeout for condwait. in ms.") 16 | fail = flag.Bool("fail", false, "operation must fail") 17 | ) 18 | 19 | const usage = ` test program for condvars. 20 | available commands: 21 | wait cond_name locker_name 22 | signal cond_name 23 | broadcast cond_name 24 | ` 25 | 26 | func makeCond(condName, lockerName string) (cond *sync.Cond, l sync.IPCLocker, err error) { 27 | l, err = sync.NewMutex(lockerName, 0, 0666) 28 | if err != nil { 29 | return 30 | } 31 | cond, err = sync.NewCond(condName, 0, 0666, l) 32 | return 33 | } 34 | 35 | func wait() error { 36 | if flag.NArg() != 4 { 37 | return fmt.Errorf("wait: must provide cond and locker name only") 38 | } 39 | ev, err := sync.NewEvent(flag.Arg(1), 0, 0666, false) 40 | if err != nil { 41 | return err 42 | } 43 | cond, l, err := makeCond(flag.Arg(2), flag.Arg(3)) 44 | if err != nil { 45 | return err 46 | } 47 | l.Lock() 48 | ev.Set() 49 | if *timeout < 0 { 50 | cond.Wait() 51 | } else { 52 | ok := cond.WaitTimeout(time.Duration(*timeout) * time.Millisecond) 53 | if ok != !*fail { 54 | return fmt.Errorf("WaitTimeout returned %v, but expected %v", ok, !*fail) 55 | } 56 | } 57 | if err1, err2 := cond.Close(), l.Close(); err1 != nil { 58 | return err1 59 | } else if err2 != nil { 60 | return err2 61 | } 62 | return nil 63 | } 64 | 65 | func signal() error { 66 | if flag.NArg() != 2 { 67 | return fmt.Errorf("signal: must provide cond name only") 68 | } 69 | condName := flag.Arg(1) 70 | cond, err := sync.NewCond(condName, 0, 0666, nil) 71 | if err != nil { 72 | return nil 73 | } 74 | cond.Signal() 75 | return cond.Close() 76 | } 77 | 78 | func broadcast() error { 79 | if flag.NArg() != 2 { 80 | return fmt.Errorf("broadcast: must provide cond name only") 81 | } 82 | condName := flag.Arg(1) 83 | cond, err := sync.NewCond(condName, 0, 0666, nil) 84 | if err != nil { 85 | return nil 86 | } 87 | cond.Broadcast() 88 | return cond.Close() 89 | } 90 | 91 | func runCommand() error { 92 | command := flag.Arg(0) 93 | switch command { 94 | case "wait": 95 | return wait() 96 | case "signal": 97 | return signal() 98 | case "broadcast": 99 | return broadcast() 100 | default: 101 | return fmt.Errorf("unknown command") 102 | } 103 | } 104 | 105 | func main() { 106 | flag.Parse() 107 | if flag.NArg() == 0 { 108 | fmt.Print(usage) 109 | flag.Usage() 110 | os.Exit(1) 111 | } 112 | if err := runCommand(); err != nil { 113 | fmt.Fprintf(os.Stderr, "%v\n", err) 114 | os.Exit(1) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /mq/mq_fast_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package mq 4 | 5 | import ( 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func fastMqCtor(name string, flag int, perm os.FileMode) (Messenger, error) { 11 | return CreateFastMq(name, flag, perm, 1, DefaultFastMqMessageSize) 12 | } 13 | 14 | func fastMqOpener(name string, flags int) (Messenger, error) { 15 | return OpenFastMq(name, flags) 16 | } 17 | 18 | func fastMqDtor(name string) error { 19 | return DestroyFastMq(name) 20 | } 21 | 22 | func fastMqCtorPrio(name string, flag int, perm os.FileMode, maxQueueSize, maxMsgSize int) (PriorityMessenger, error) { 23 | return CreateFastMq(name, flag, perm, maxQueueSize, maxMsgSize) 24 | } 25 | 26 | func fastMqOpenerPrio(name string, flags int) (PriorityMessenger, error) { 27 | return OpenFastMq(name, flags) 28 | } 29 | 30 | func TestCreateFastMq(t *testing.T) { 31 | testCreateMq(t, fastMqCtor, fastMqDtor) 32 | } 33 | 34 | func TestCreateFastMqExcl(t *testing.T) { 35 | testCreateMqExcl(t, fastMqCtor, fastMqDtor) 36 | } 37 | 38 | func TestCreateFastMqInvalidPerm(t *testing.T) { 39 | testCreateMqInvalidPerm(t, fastMqCtor, fastMqDtor) 40 | } 41 | 42 | func TestOpenFastMq(t *testing.T) { 43 | testOpenMq(t, fastMqCtor, fastMqOpener, fastMqDtor) 44 | } 45 | 46 | func TestFastMqSendIntSameProcess(t *testing.T) { 47 | testMqSendIntSameProcess(t, fastMqCtor, fastMqOpener, fastMqDtor) 48 | } 49 | 50 | func TestFastMqSendNonBlock(t *testing.T) { 51 | testMqSendNonBlock(t, fastMqCtor, fastMqDtor) 52 | } 53 | 54 | func TestFastMqSendToAnotherProcess(t *testing.T) { 55 | testMqSendToAnotherProcess(t, fastMqCtor, fastMqDtor, "fast") 56 | } 57 | 58 | func TestFastMqPrio1(t *testing.T) { 59 | testPrioMq1(t, fastMqCtorPrio, fastMqOpenerPrio, fastMqDtor) 60 | } 61 | 62 | func TestFastMqSendStructSameProcess(t *testing.T) { 63 | testMqSendStructSameProcess(t, fastMqCtor, fastMqOpener, fastMqDtor) 64 | } 65 | 66 | func TestFastMqSendMessageLessThenBuffer(t *testing.T) { 67 | testMqSendMessageLessThenBuffer(t, fastMqCtor, fastMqOpener, fastMqDtor) 68 | } 69 | 70 | func TestFastMqReceiveFromAnotherProcess(t *testing.T) { 71 | testMqReceiveFromAnotherProcess(t, fastMqCtor, fastMqDtor, "fast") 72 | } 73 | 74 | func TestFastMqSendTimeout(t *testing.T) { 75 | testMqSendTimeout(t, fastMqCtor, fastMqDtor) 76 | } 77 | 78 | func TestFastMqReceiveTimeout(t *testing.T) { 79 | testMqReceiveTimeout(t, fastMqCtor, fastMqDtor) 80 | } 81 | 82 | func BenchmarkFastMqNonBlock(b *testing.B) { 83 | params := &prioBenchmarkParams{readers: 4, writers: 4, mqSize: 8, msgSize: 1024, flag: O_NONBLOCK} 84 | benchmarkPrioMq1(b, fastMqCtorPrio, fastMqOpenerPrio, fastMqDtor, params) 85 | } 86 | 87 | func BenchmarkFastMqBlock(b *testing.B) { 88 | params := &prioBenchmarkParams{readers: 4, writers: 4, mqSize: 8, msgSize: 1024, flag: 0} 89 | benchmarkPrioMq1(b, fastMqCtorPrio, fastMqOpenerPrio, fastMqDtor, params) 90 | } 91 | -------------------------------------------------------------------------------- /sync/sync_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "math/rand" 7 | "os" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | func ExampleIPCLocker() { 13 | DestroyMutex("mut") 14 | mut, err := NewMutex("mut", os.O_CREATE|os.O_EXCL, 0666) 15 | if err != nil { 16 | panic("new") 17 | } 18 | defer mut.Close() 19 | var sharedValue uint64 20 | var wg sync.WaitGroup 21 | wg.Add(8) 22 | for i := 0; i < 8; i++ { 23 | go func() { 24 | defer wg.Done() 25 | mut, err := NewMutex("mut", 0, 0) 26 | if err != nil { 27 | panic("new") 28 | } 29 | defer mut.Close() 30 | for i := 0; i < 1000000; i++ { 31 | mut.Lock() 32 | sharedValue++ 33 | mut.Unlock() 34 | } 35 | }() 36 | } 37 | wg.Wait() 38 | if sharedValue != 8*1000000 { 39 | panic("invalid value ") 40 | } 41 | } 42 | 43 | func ExampleTimedIPCLocker() { 44 | DestroyMutex("mut") 45 | mut, err := NewMutex("mut", os.O_CREATE|os.O_EXCL, 0666) 46 | if err != nil { 47 | panic("new") 48 | } 49 | defer mut.Close() 50 | tmut, ok := mut.(TimedIPCLocker) 51 | if !ok { 52 | panic("not a timed locker") 53 | } 54 | var sharedValue int 55 | rand.Seed(time.Now().Unix()) 56 | go func() { 57 | mut, err := NewMutex("mut", 0, 0) 58 | if err != nil { 59 | panic("new") 60 | } 61 | defer mut.Close() 62 | mut.Lock() 63 | // change value after [0..500] ms delay. 64 | time.Sleep(time.Duration(rand.Int()%6) * time.Millisecond * 100) 65 | sharedValue = 1 66 | mut.Unlock() 67 | }() 68 | // give another goroutine some time to lock the mutex. 69 | time.Sleep(10 * time.Millisecond) 70 | if tmut.LockTimeout(250 * time.Millisecond) { 71 | if sharedValue != 1 { 72 | panic("bad value") 73 | } 74 | tmut.Unlock() 75 | } else { 76 | } // timeout elapsed 77 | } 78 | 79 | func ExampleCond() { 80 | DestroyMutex("mut") 81 | mut, err := NewMutex("mut", os.O_CREATE|os.O_EXCL, 0666) 82 | if err != nil { 83 | panic("new") 84 | } 85 | defer mut.Close() 86 | DestroyCond("cond") 87 | cond, err := NewCond("cond", os.O_CREATE|os.O_EXCL, 0666, mut) 88 | if err != nil { 89 | panic("new") 90 | } 91 | defer cond.Close() 92 | var sharedValue int 93 | go func() { 94 | mut.Lock() 95 | defer mut.Unlock() 96 | sharedValue = 1 97 | cond.Signal() 98 | }() 99 | mut.Lock() 100 | defer mut.Unlock() 101 | if sharedValue == 0 { 102 | cond.Wait() 103 | if sharedValue == 0 { 104 | panic("bad value") 105 | } 106 | } 107 | } 108 | 109 | func ExampleEvent() { 110 | event, err := NewEvent("event", os.O_CREATE|os.O_EXCL, 0666, false) 111 | if err != nil { 112 | return 113 | } 114 | go func() { 115 | event.Set() 116 | }() 117 | if event.WaitTimeout(time.Millisecond * 250) { 118 | // event has been set 119 | } else { 120 | // timeout elapsed 121 | } 122 | event.Destroy() 123 | } 124 | -------------------------------------------------------------------------------- /sync/mutex_futex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build linux freebsd 4 | 5 | package sync 6 | 7 | import ( 8 | "os" 9 | "time" 10 | 11 | "bitbucket.org/avd/go-ipc/internal/allocator" 12 | "bitbucket.org/avd/go-ipc/internal/helper" 13 | "bitbucket.org/avd/go-ipc/mmf" 14 | "bitbucket.org/avd/go-ipc/shm" 15 | 16 | "github.com/pkg/errors" 17 | ) 18 | 19 | // all implementations must satisfy at least IPCLocker interface. 20 | var ( 21 | _ TimedIPCLocker = (*FutexMutex)(nil) 22 | ) 23 | 24 | // FutexMutex is a mutex based on linux/freebsd futex object. 25 | type FutexMutex struct { 26 | lwm *lwMutex 27 | region *mmf.MemoryRegion 28 | name string 29 | } 30 | 31 | // NewFutexMutex creates a new futex-based mutex. 32 | // This implementation is based on a paper 'Futexes Are Tricky' by Ulrich Drepper, 33 | // this document can be found in 'docs' folder. 34 | // name - object name. 35 | // flag - flag is a combination of open flags from 'os' package. 36 | // perm - object's permission bits. 37 | func NewFutexMutex(name string, flag int, perm os.FileMode) (*FutexMutex, error) { 38 | if err := ensureOpenFlags(flag); err != nil { 39 | return nil, err 40 | } 41 | region, created, err := helper.CreateWritableRegion(mutexSharedStateName(name, "f"), flag, perm, lwmStateSize) 42 | if err != nil { 43 | return nil, errors.Wrap(err, "failed to create shared state") 44 | } 45 | 46 | data := allocator.ByteSliceData(region.Data()) 47 | result := &FutexMutex{ 48 | region: region, 49 | name: name, 50 | lwm: newLightweightMutex(data, &futex{ptr: data}), 51 | } 52 | if created { 53 | result.lwm.init() 54 | } 55 | return result, nil 56 | } 57 | 58 | // Lock locks the mutex. It panics on an error. 59 | func (f *FutexMutex) Lock() { 60 | f.lwm.lock() 61 | } 62 | 63 | // TryLock makes one attempt to lock the mutex. It return true on succeess and false otherwise. 64 | func (f *FutexMutex) TryLock() bool { 65 | return f.lwm.tryLock() 66 | } 67 | 68 | // LockTimeout tries to lock the locker, waiting for not more, than timeout. 69 | func (f *FutexMutex) LockTimeout(timeout time.Duration) bool { 70 | return f.lwm.lockTimeout(timeout) 71 | } 72 | 73 | // Unlock releases the mutex. It panics on an error, or if the mutex is not locked. 74 | func (f *FutexMutex) Unlock() { 75 | f.lwm.unlock() 76 | } 77 | 78 | // Close indicates, that the object is no longer in use, 79 | // and that the underlying resources can be freed. 80 | func (f *FutexMutex) Close() error { 81 | return f.region.Close() 82 | } 83 | 84 | // Destroy removes the mutex object. 85 | func (f *FutexMutex) Destroy() error { 86 | if err := f.Close(); err != nil { 87 | return errors.Wrap(err, "failed to close shm region") 88 | } 89 | return DestroyFutexMutex(f.name) 90 | } 91 | 92 | // DestroyFutexMutex permanently removes mutex with the given name. 93 | func DestroyFutexMutex(name string) error { 94 | if err := shm.DestroyMemoryObject(mutexSharedStateName(name, "f")); err != nil { 95 | return errors.Wrap(err, "failed to destroy memory object") 96 | } 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /sync/rwmutex_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "fmt" 7 | "math/rand" 8 | "os" 9 | "sync" 10 | "testing" 11 | ) 12 | 13 | func rwMutexCtor(name string, flag int, perm os.FileMode) (IPCLocker, error) { 14 | return NewRWMutex(name, flag, perm) 15 | } 16 | 17 | func rwRMutexCtor(name string, flag int, perm os.FileMode) (IPCLocker, error) { 18 | locker, err := NewRWMutex(name, flag, perm) 19 | if err != nil { 20 | return nil, err 21 | } 22 | return locker.RLocker(), nil 23 | } 24 | 25 | func rwMutexDtor(name string) error { 26 | return DestroyRWMutex(name) 27 | } 28 | 29 | func TestRWMutexOpenMode(t *testing.T) { 30 | testLockerOpenMode(t, rwMutexCtor, rwMutexDtor) 31 | } 32 | 33 | func TestRWMutexOpenMode2(t *testing.T) { 34 | testLockerOpenMode2(t, rwMutexCtor, rwMutexDtor) 35 | } 36 | 37 | func TestRWMutexOpenMode3(t *testing.T) { 38 | testLockerOpenMode3(t, rwMutexCtor, rwMutexDtor) 39 | } 40 | 41 | func TestRWMutexOpenMode4(t *testing.T) { 42 | testLockerOpenMode4(t, rwMutexCtor, rwMutexDtor) 43 | } 44 | 45 | func TestRWMutexOpenMode5(t *testing.T) { 46 | testLockerOpenMode5(t, rwMutexCtor, rwMutexDtor) 47 | } 48 | 49 | func TestRWMutexLock(t *testing.T) { 50 | testLockerLock(t, rwMutexCtor, rwMutexDtor) 51 | } 52 | 53 | func TestRWMutexMemory(t *testing.T) { 54 | testLockerMemory(t, "rw", false, rwMutexCtor, rwMutexDtor) 55 | } 56 | 57 | func TestRWMutexMemory2(t *testing.T) { 58 | testLockerMemory(t, "rw", true, rwMutexCtor, rwMutexDtor) 59 | } 60 | 61 | func TestRWMutexValueInc(t *testing.T) { 62 | testLockerValueInc(t, "rw", rwMutexCtor, rwMutexDtor) 63 | } 64 | 65 | func TestRWMutexPanicsOnDoubleUnlock(t *testing.T) { 66 | testLockerTwiceUnlock(t, rwMutexCtor, rwMutexDtor) 67 | } 68 | 69 | func TestRWMutexPanicsOnDoubleRUnlock(t *testing.T) { 70 | testLockerTwiceUnlock(t, rwRMutexCtor, rwMutexDtor) 71 | } 72 | 73 | func ExampleRWMutex() { 74 | const ( 75 | writers = 4 76 | readers = 10 77 | ) 78 | DestroyRWMutex("rw") 79 | m, err := NewRWMutex("rw", os.O_CREATE|os.O_EXCL, 0666) 80 | if err != nil { 81 | panic(err) 82 | } 83 | // we create a shared array of consistently increasing ints for reading and wriring. 84 | sharedData := make([]int, 128) 85 | for i := range sharedData { 86 | sharedData[i] = i 87 | } 88 | var wg sync.WaitGroup 89 | wg.Add(writers + readers) 90 | // writers will update the data. 91 | for i := 0; i < writers; i++ { 92 | go func() { 93 | defer wg.Done() 94 | start := rand.Intn(1024) 95 | m.Lock() 96 | for i := range sharedData { 97 | sharedData[i] = i + start 98 | } 99 | m.Unlock() 100 | }() 101 | } 102 | // readers will check the data. 103 | for i := 0; i < readers; i++ { 104 | go func() { 105 | defer wg.Done() 106 | m.RLock() 107 | for i := 1; i < len(sharedData); i++ { 108 | if sharedData[i] != sharedData[i-1]+1 { 109 | panic("bad data") 110 | } 111 | } 112 | m.RUnlock() 113 | }() 114 | } 115 | wg.Wait() 116 | fmt.Println("done") 117 | // Output: 118 | // done 119 | } 120 | -------------------------------------------------------------------------------- /mq/mq_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package mq 4 | 5 | import ( 6 | "math/rand" 7 | "os" 8 | "time" 9 | ) 10 | 11 | func ExampleMessenger() { 12 | Destroy("mq") 13 | mq, err := New("mq", os.O_CREATE|os.O_EXCL, 0666) 14 | if err != nil { 15 | panic("new queue") 16 | } 17 | defer mq.Close() 18 | data := []byte{1, 2, 3, 4, 5, 6, 7, 8} 19 | go func() { 20 | if err := mq.Send(data); err != nil { 21 | panic("send") 22 | } 23 | }() 24 | mq2, err := Open("mq", 0) 25 | if err != nil { 26 | panic("open") 27 | } 28 | defer mq2.Close() 29 | received := make([]byte, len(data)) 30 | l, err := mq2.Receive(received) 31 | if err != nil { 32 | panic("receive") 33 | } 34 | if l != len(data) { 35 | panic("wrong len") 36 | } 37 | for i, b := range received { 38 | if b != data[i] { 39 | panic("wrong data") 40 | } 41 | } 42 | } 43 | 44 | func ExampleTimedMessenger() { 45 | Destroy("mq") 46 | mq, err := New("mq", os.O_CREATE|os.O_EXCL, 0666) 47 | if err != nil { 48 | panic("new queue") 49 | } 50 | defer mq.Close() 51 | data := []byte{1, 2, 3, 4, 5, 6, 7, 8} 52 | go func() { 53 | // send after [0..500] ms delay. 54 | time.Sleep(time.Duration((rand.Int() % 6)) * time.Millisecond * 100) 55 | if err := mq.Send(data); err != nil { 56 | panic("send") 57 | } 58 | }() 59 | mq2, err := Open("mq", 0) 60 | if err != nil { 61 | panic("open") 62 | } 63 | defer mq2.Close() 64 | // not all implementations support timed send/receive. 65 | tmq, ok := mq2.(TimedMessenger) 66 | if !ok { 67 | panic("not a timed messenger") 68 | } 69 | received := make([]byte, len(data)) 70 | // depending on send delay we either get a timeout error, or receive the data. 71 | l, err := tmq.ReceiveTimeout(received, 500*time.Millisecond) 72 | if err != nil { 73 | if !IsTemporary(err) { 74 | panic(err) 75 | } else { // handle timeout. 76 | return 77 | } 78 | } 79 | if l != len(data) { 80 | panic("wrong len") 81 | } 82 | for i, b := range received { 83 | if b != data[i] { 84 | panic("wrong data") 85 | } 86 | } 87 | } 88 | 89 | func ExamplePriorityMessenger() { 90 | Destroy("mq") 91 | mq, err := New("mq", os.O_CREATE|os.O_EXCL, 0666) 92 | if err != nil { 93 | panic("new queue") 94 | } 95 | defer mq.Close() 96 | // not all implementations support prioritized send/receive. 97 | tmq, ok := mq.(PriorityMessenger) 98 | if !ok { 99 | panic("not a prio messenger") 100 | } 101 | data := []byte{1, 2, 3, 4, 5, 6, 7, 8} 102 | go func() { 103 | if err := tmq.SendPriority(data, 0); err != nil { 104 | panic("send") 105 | } 106 | if err := tmq.SendPriority(data, 1); err != nil { 107 | panic("send") 108 | } 109 | }() 110 | mq2, err := Open("mq", 0) 111 | if err != nil { 112 | panic("open") 113 | } 114 | defer mq2.Close() 115 | tmq2, ok := mq2.(PriorityMessenger) 116 | if !ok { 117 | panic("not a prio messenger") 118 | } 119 | received := make([]byte, len(data)) 120 | _, prio, err := tmq2.ReceivePriority(received) 121 | if err != nil || prio != 1 { 122 | panic("receive") 123 | } 124 | _, prio, err = tmq2.ReceivePriority(received) 125 | if err != nil || prio != 0 { 126 | panic("receive") 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /sync/mutex_spin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "os" 7 | "runtime" 8 | "time" 9 | 10 | "bitbucket.org/avd/go-ipc/internal/allocator" 11 | "bitbucket.org/avd/go-ipc/internal/helper" 12 | "bitbucket.org/avd/go-ipc/mmf" 13 | "bitbucket.org/avd/go-ipc/shm" 14 | 15 | "github.com/pkg/errors" 16 | ) 17 | 18 | // all implementations must satisfy IPCLocker interface. 19 | var ( 20 | _ IPCLocker = (*SpinMutex)(nil) 21 | ) 22 | 23 | // SpinMutex is a synchronization object which performs busy wait loop. 24 | type SpinMutex struct { 25 | lwm *lwMutex 26 | region *mmf.MemoryRegion 27 | name string 28 | } 29 | 30 | type spinWW struct{} 31 | 32 | func (sw spinWW) wake(int32) (int, error) { 33 | return 1, nil 34 | } 35 | 36 | func (sw spinWW) wait(unused int32, timeout time.Duration) error { 37 | runtime.Gosched() 38 | return nil 39 | } 40 | 41 | // NewSpinMutex creates a new spin mutex. 42 | // name - object name. 43 | // flag - flag is a combination of open flags from 'os' package. 44 | // perm - object's permission bits. 45 | func NewSpinMutex(name string, flag int, perm os.FileMode) (*SpinMutex, error) { 46 | if err := ensureOpenFlags(flag); err != nil { 47 | return nil, err 48 | } 49 | name = spinName(name) 50 | region, created, err := helper.CreateWritableRegion(name, flag, perm, lwmStateSize) 51 | if err != nil { 52 | return nil, err 53 | } 54 | result := &SpinMutex{ 55 | region: region, 56 | name: name, 57 | lwm: newLightweightMutex(allocator.ByteSliceData(region.Data()), new(spinWW)), 58 | } 59 | if created { 60 | result.lwm.init() 61 | } 62 | return result, nil 63 | } 64 | 65 | // Lock locks the mutex waiting in a busy loop if needed. 66 | func (spin *SpinMutex) Lock() { 67 | spin.lwm.lock() 68 | } 69 | 70 | // LockTimeout locks the mutex waiting in a busy loop for not longer, than timeout. 71 | func (spin *SpinMutex) LockTimeout(timeout time.Duration) bool { 72 | return spin.lwm.lockTimeout(timeout) 73 | } 74 | 75 | // Unlock releases the mutex. It panics, if the mutex is not locked. 76 | func (spin *SpinMutex) Unlock() { 77 | spin.lwm.unlock() 78 | } 79 | 80 | // TryLock makes one attempt to lock the mutex. It return true on succeess and false otherwise. 81 | func (spin *SpinMutex) TryLock() bool { 82 | return spin.lwm.tryLock() 83 | } 84 | 85 | // Close indicates, that the object is no longer in use, 86 | // and that the underlying resources can be freed. 87 | func (spin *SpinMutex) Close() error { 88 | return spin.region.Close() 89 | } 90 | 91 | // Destroy removes the mutex object. 92 | func (spin *SpinMutex) Destroy() error { 93 | if err := spin.Close(); err != nil { 94 | return errors.Wrap(err, "failed to close spin mutex") 95 | } 96 | spin.region = nil 97 | err := shm.DestroyMemoryObject(spin.name) 98 | spin.name = "" 99 | if err != nil { 100 | return errors.Wrap(err, "failed to destroy shm object") 101 | } 102 | return nil 103 | } 104 | 105 | // DestroySpinMutex removes a mutex object with the given name 106 | func DestroySpinMutex(name string) error { 107 | return shm.DestroyMemoryObject(spinName(name)) 108 | } 109 | 110 | func spinName(name string) string { 111 | return "go-ipc.spin." + name 112 | } 113 | -------------------------------------------------------------------------------- /mmf/memory_region_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build darwin freebsd linux 4 | 5 | package mmf 6 | 7 | import ( 8 | "os" 9 | "syscall" 10 | "unsafe" 11 | 12 | "bitbucket.org/avd/go-ipc/internal/allocator" 13 | 14 | "github.com/pkg/errors" 15 | "golang.org/x/sys/unix" 16 | ) 17 | 18 | func init() { 19 | mmapOffsetMultiple = int64(os.Getpagesize()) 20 | } 21 | 22 | type memoryRegion struct { 23 | data []byte 24 | size int 25 | pageOffset int64 26 | } 27 | 28 | func newMemoryRegion(obj Mappable, flag int, offset int64, size int) (*memoryRegion, error) { 29 | prot, flags, err := memProtAndFlagsFromMode(flag) 30 | if err != nil { 31 | return nil, errors.Wrap(err, "memory region flags check failed") 32 | } 33 | if size, err = checkMmapSize(obj, size); err != nil { 34 | return nil, errors.Wrap(err, "size check failed") 35 | } 36 | calculatedSize, err := fileSizeFromFd(obj) 37 | if err != nil { 38 | return nil, errors.Wrap(err, "file size check failed") 39 | } 40 | // we need this check on unix, because you can actually mmap more bytes, 41 | // then the size of the object, which can cause unexpected problems. 42 | if calculatedSize > 0 && int64(size)+offset > calculatedSize { 43 | return nil, errors.New("invalid mapping length") 44 | } 45 | pageOffset := calcMmapOffsetFixup(offset) 46 | var data []byte 47 | if data, err = unix.Mmap(int(obj.Fd()), offset-pageOffset, size+int(pageOffset), prot, flags); err != nil { 48 | return nil, errors.Wrap(err, "mmap failed") 49 | } 50 | return &memoryRegion{data: data, size: size, pageOffset: pageOffset}, nil 51 | } 52 | 53 | func (region *memoryRegion) Close() error { 54 | if region.data != nil { 55 | err := unix.Munmap(region.data) 56 | region.data = nil 57 | region.pageOffset = 0 58 | region.size = 0 59 | return errors.Wrap(err, "munmap failed") 60 | } 61 | return nil 62 | } 63 | 64 | func (region *memoryRegion) Data() []byte { 65 | return region.data[region.pageOffset:] 66 | } 67 | 68 | func (region *memoryRegion) Flush(async bool) error { 69 | flag := unix.MS_SYNC 70 | if async { 71 | flag = unix.MS_ASYNC 72 | } 73 | if err := msync(region.data, flag); err != nil { 74 | return errors.Wrap(err, "mync failed") 75 | } 76 | return nil 77 | } 78 | 79 | func (region *memoryRegion) Size() int { 80 | return region.size 81 | } 82 | 83 | func memProtAndFlagsFromMode(mode int) (prot, flags int, err error) { 84 | switch mode { 85 | case MEM_READ_ONLY: 86 | prot = unix.PROT_READ 87 | flags = unix.MAP_SHARED 88 | case MEM_READ_PRIVATE: 89 | prot = unix.PROT_READ 90 | flags = unix.MAP_PRIVATE 91 | case MEM_READWRITE: 92 | prot = unix.PROT_READ | unix.PROT_WRITE 93 | flags = unix.MAP_SHARED 94 | case MEM_COPY_ON_WRITE: 95 | prot = unix.PROT_READ | unix.PROT_WRITE 96 | flags = unix.MAP_PRIVATE 97 | default: 98 | err = errors.Errorf("invalid memory region flags %d", mode) 99 | } 100 | return 101 | } 102 | 103 | // syscalls 104 | func msync(data []byte, flags int) error { 105 | dataPointer := unsafe.Pointer(&data[0]) 106 | _, _, err := unix.Syscall(unix.SYS_MSYNC, uintptr(dataPointer), uintptr(len(data)), uintptr(flags)) 107 | allocator.Use(dataPointer) 108 | if err != syscall.Errno(0) { 109 | return err 110 | } 111 | return nil 112 | } 113 | -------------------------------------------------------------------------------- /internal/common/common.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package common 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "syscall" 9 | "time" 10 | ) 11 | 12 | const ( 13 | // O_NONBLOCK flag tell some functions not to block. 14 | // Its value does not interfere with O_* constants from 'os' package. 15 | O_NONBLOCK = syscall.O_NONBLOCK 16 | ) 17 | 18 | // Destroyer is an object which can be permanently removed. 19 | type Destroyer interface { 20 | Destroy() error 21 | } 22 | 23 | // FlagsForOpen extracts os.O_CREATE and os.O_EXCL flag values. 24 | func FlagsForOpen(flag int) int { 25 | return flag & (os.O_CREATE | os.O_EXCL) 26 | } 27 | 28 | // FlagsForAccess extracts os.O_RDONLY, os.O_WRONLY, os.O_RDWR, and O_NONBLOCK flag values. 29 | func FlagsForAccess(flag int) int { 30 | return flag & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR | O_NONBLOCK) 31 | } 32 | 33 | // OpenOrCreate performs open/create file operation according to the given mode. 34 | // It allows to find out if the object was opened or created. 35 | // creator is the function which performs actual operation: 36 | // if is called with 'true', if it must create an object, and with false otherwise. 37 | // it must return an 'not exists error' if the param is false, and the object does not exist. 38 | // it must return an 'already exists error' if the param is true, and the object already exists. 39 | // flag is the combination of open flags from os package. 40 | // If flag == os.O_CREATE, OpenOrCreate makes several attempts to open or create an object, 41 | // and analyzes the return error. It tries to open the object first. 42 | func OpenOrCreate(creator func(bool) error, flag int) (bool, error) { 43 | flag = FlagsForOpen(flag) 44 | switch flag { 45 | case 0: 46 | return false, creator(false) 47 | case os.O_CREATE | os.O_EXCL: 48 | err := creator(true) 49 | if err != nil { 50 | return false, err 51 | } 52 | return true, nil 53 | case os.O_CREATE: 54 | const attempts = 16 55 | var err error 56 | for attempt := 0; attempt < attempts; attempt++ { 57 | if err = creator(false); !os.IsNotExist(err) { 58 | return false, err 59 | } 60 | if err = creator(true); !os.IsExist(err) { 61 | return true, err 62 | } 63 | } 64 | return false, err 65 | default: 66 | return false, fmt.Errorf("unknown open mode") 67 | } 68 | } 69 | 70 | // SyscallErrHasCode returns true, if given error is a syscall error with given code. 71 | func SyscallErrHasCode(err error, code syscall.Errno) bool { 72 | if sysErr, ok := err.(*os.SyscallError); ok { 73 | if errno, ok := sysErr.Err.(syscall.Errno); ok { 74 | return errno == code 75 | } 76 | } 77 | return false 78 | } 79 | 80 | // CallTimeout calls f in a loop allowing it to run for at least 'timeout'. 81 | // It calls f, measuring its runtime: 82 | // if f returned false, or its cumulative runtime exceeded timeout, CallTimeout returns. 83 | // otherwise CallTimeout subtracts runtime from timeout anf calls 'f' with the updated value. 84 | func CallTimeout(f func(time.Duration) bool, timeout time.Duration) { 85 | for { 86 | opStart := time.Now() 87 | if !f(timeout) { 88 | return 89 | } 90 | if timeout >= 0 { 91 | elapsed := time.Since(opStart) 92 | if timeout > elapsed { 93 | timeout = timeout - elapsed 94 | } else { 95 | return 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /internal/array/shared_array_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package array 4 | 5 | import ( 6 | "math/rand" 7 | "sort" 8 | "testing" 9 | "time" 10 | 11 | "bitbucket.org/avd/go-ipc/internal/allocator" 12 | 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestSharedArray(t *testing.T) { 17 | a := assert.New(t) 18 | sl := make([]byte, CalcSharedArraySize(10, 8)) 19 | arr := NewSharedArray(allocator.ByteSliceData(sl), 10, 8) 20 | a.Equal(arr.Len(), 0) 21 | a.Panics(func() { 22 | arr.PopFront() 23 | }) 24 | data := make([]byte, 1) 25 | for i := 0; i < 10; i++ { 26 | data[0] = byte(i) 27 | arr.PushBack(data) 28 | } 29 | a.Equal(arr.Len(), 10) 30 | a.Panics(func() { 31 | arr.PushBack() 32 | }) 33 | for i := 0; i < 10; i++ { 34 | data[0] = byte(i) 35 | a.Equal(data, arr.At(i)) 36 | } 37 | a.Equal(10, arr.Len()) 38 | a.Panics(func() { 39 | arr.PushBack(data) 40 | }) 41 | a.NotPanics(func() { 42 | copy(data, arr.At(0)) 43 | arr.PopFront() 44 | }) 45 | a.Equal(arr.Len(), 9) 46 | a.Equal([]byte{0}, data) 47 | for i := 0; i < 9; i++ { 48 | data[0] = byte(i + 1) 49 | a.Equal(data, arr.At(i)) 50 | } 51 | arr.Swap(0, 8) 52 | a.Equal(arr.Len(), 9) 53 | a.Equal([]byte{9}, arr.At(0)) 54 | a.Equal([]byte{1}, arr.At(8)) 55 | l := arr.Len() 56 | for i := 0; i < l; i++ { 57 | a.NotPanics(func() { 58 | a.Equal(arr.Len(), 9-i) 59 | arr.PopFront() 60 | }) 61 | } 62 | a.Equal(0, arr.Len()) 63 | a.NotPanics(func() { 64 | data[0] = 13 65 | arr.PushBack(data) 66 | }) 67 | a.NotPanics(func() { 68 | data[0] = 255 69 | arr.PushBack(data) 70 | }) 71 | a.NotPanics(func() { 72 | arr.Swap(0, 1) 73 | }) 74 | a.Equal(arr.Len(), 2) 75 | a.Equal([]byte{255}, arr.At(0)) 76 | a.Equal([]byte{13}, arr.At(1)) 77 | } 78 | 79 | type sorter struct { 80 | a *SharedArray 81 | } 82 | 83 | func (s sorter) Len() int { 84 | return s.a.Len() 85 | } 86 | 87 | func (s sorter) Less(i, j int) bool { 88 | l, r := s.a.At(i), s.a.At(j) 89 | return l[0] < r[0] 90 | } 91 | 92 | func (s sorter) Swap(i, j int) { 93 | s.a.Swap(i, j) 94 | } 95 | 96 | func TestSharedArray2(t *testing.T) { 97 | data := [...]int{8, 4, 7, 1, 0, 15, 2, 4, 10} 98 | a := assert.New(t) 99 | sl := make([]byte, CalcSharedArraySize(len(data), 8)) 100 | arr := NewSharedArray(allocator.ByteSliceData(sl), len(data), 8) 101 | for i := 0; i < len(data); i++ { 102 | arr.PushBack([]byte{byte(data[i])}) 103 | } 104 | for outer := 1; outer < len(data); outer++ { 105 | sort.Ints(data[outer:]) 106 | arr.PopFront() 107 | sort.Sort(&sorter{a: arr}) 108 | for i, b := range data[outer:] { 109 | a.Equal(byte(b), arr.At(i)[0]) 110 | } 111 | } 112 | } 113 | 114 | func TestSharedArray3(t *testing.T) { 115 | data := [...]int{8, 4, 7, 1, 0, 15, 2, 4, 10} 116 | a := assert.New(t) 117 | sl := make([]byte, CalcSharedArraySize(len(data), 8)) 118 | arr := NewSharedArray(allocator.ByteSliceData(sl), len(data), 8) 119 | for i := 0; i < len(data); i++ { 120 | arr.PushBack([]byte{byte(data[i])}) 121 | } 122 | d := data[:] 123 | rand.Seed(time.Now().Unix()) 124 | for i := 0; i < len(data); i++ { 125 | idx := rand.Intn(len(d)) 126 | popped := arr.At(idx) 127 | a.Equal(d[idx], int(popped[0])) 128 | arr.RemoveAt(idx) 129 | d = append(d[:idx], d[idx+1:]...) 130 | } 131 | a.Equal(0, arr.Len()) 132 | } 133 | -------------------------------------------------------------------------------- /sync/mutex_sema.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "os" 7 | "time" 8 | 9 | "bitbucket.org/avd/go-ipc/internal/allocator" 10 | "bitbucket.org/avd/go-ipc/internal/helper" 11 | "bitbucket.org/avd/go-ipc/mmf" 12 | "bitbucket.org/avd/go-ipc/shm" 13 | 14 | "github.com/pkg/errors" 15 | ) 16 | 17 | // all implementations must satisfy IPCLocker interface. 18 | var ( 19 | _ IPCLocker = (*SemaMutex)(nil) 20 | ) 21 | 22 | // SemaMutex is a semaphore-based mutex for unix. 23 | type SemaMutex struct { 24 | s *Semaphore 25 | region *mmf.MemoryRegion 26 | name string 27 | lwm *lwMutex 28 | } 29 | 30 | // NewSemaMutex creates a new semaphore-based mutex. 31 | // name - object name. 32 | // flag - flag is a combination of open flags from 'os' package. 33 | // perm - object's permission bits. 34 | func NewSemaMutex(name string, flag int, perm os.FileMode) (*SemaMutex, error) { 35 | if err := ensureOpenFlags(flag); err != nil { 36 | return nil, err 37 | } 38 | region, created, err := helper.CreateWritableRegion(mutexSharedStateName(name, "s"), flag, perm, lwmStateSize) 39 | if err != nil { 40 | return nil, errors.Wrap(err, "failed to create shared state") 41 | } 42 | s, err := NewSemaphore(name, flag, perm, 1) 43 | if err != nil { 44 | region.Close() 45 | if created { 46 | shm.DestroyMemoryObject(mutexSharedStateName(name, "s")) 47 | } 48 | return nil, errors.Wrap(err, "failed to create a semaphore") 49 | } 50 | result := &SemaMutex{ 51 | s: s, 52 | region: region, 53 | name: name, 54 | lwm: newLightweightMutex(allocator.ByteSliceData(region.Data()), newSemaWaiter(s)), 55 | } 56 | if created { 57 | result.lwm.init() 58 | } 59 | return result, nil 60 | } 61 | 62 | // Lock locks the mutex. It panics on an error. 63 | func (m *SemaMutex) Lock() { 64 | m.lwm.lock() 65 | } 66 | 67 | // LockTimeout tries to lock the locker, waiting for not more, than timeout. 68 | func (m *SemaMutex) LockTimeout(timeout time.Duration) bool { 69 | return m.lwm.lockTimeout(timeout) 70 | } 71 | 72 | // TryLock makes one attempt to lock the mutex. It returns true on succeess and false otherwise. 73 | func (m *SemaMutex) TryLock() bool { 74 | return m.lwm.tryLock() 75 | } 76 | 77 | // Unlock releases the mutex. It panics on an error, or if the mutex is not locked. 78 | func (m *SemaMutex) Unlock() { 79 | m.lwm.unlock() 80 | } 81 | 82 | // Close closes shared state of the mutex. 83 | func (m *SemaMutex) Close() error { 84 | e1, e2 := m.s.Close(), m.region.Close() 85 | if e1 != nil { 86 | return errors.Wrap(e1, "failed to close semaphore") 87 | } 88 | if e2 != nil { 89 | return errors.Wrap(e2, "failed to close shared state") 90 | } 91 | return nil 92 | } 93 | 94 | // Destroy closes the mutex and removes it permanently. 95 | func (m *SemaMutex) Destroy() error { 96 | if err := m.Close(); err != nil { 97 | return errors.Wrap(err, "failed to close shared state") 98 | } 99 | return DestroySemaMutex(m.name) 100 | } 101 | 102 | // DestroySemaMutex permanently removes mutex with the given name. 103 | func DestroySemaMutex(name string) error { 104 | if err := shm.DestroyMemoryObject(mutexSharedStateName(name, "s")); err != nil { 105 | return errors.Wrap(err, "failed to destroy shared state") 106 | } 107 | if err := DestroySemaphore(name); err != nil && !os.IsNotExist(errors.Cause(err)) { 108 | return err 109 | } 110 | return nil 111 | } 112 | -------------------------------------------------------------------------------- /internal/sys/windows/syscall_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sys 4 | 5 | import ( 6 | "os" 7 | "syscall" 8 | "unsafe" 9 | 10 | "bitbucket.org/avd/go-ipc/internal/allocator" 11 | 12 | "golang.org/x/sys/windows" 13 | ) 14 | 15 | // systemInfo is used for GetSystemInfo WinApi call 16 | // see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724958(v=vs.85).aspx 17 | type systemInfo struct { 18 | // This is the first member of the union 19 | OemID uint32 20 | // These are the second member of the union 21 | // ProcessorArchitecture uint16; 22 | // Reserved uint16; 23 | PageSize uint32 24 | MinimumApplicationAddress uintptr 25 | MaximumApplicationAddress uintptr 26 | ActiveProcessorMask *uint32 27 | NumberOfProcessors uint32 28 | ProcessorType uint32 29 | AllocationGranularity uint32 30 | ProcessorLevel uint16 31 | ProcessorRevision uint16 32 | } 33 | 34 | var ( 35 | modkernel32 = windows.NewLazyDLL("kernel32.dll") 36 | procGetSystemInfo = modkernel32.NewProc("GetSystemInfo") 37 | procOpenFileMapping = modkernel32.NewProc("OpenFileMappingW") 38 | procCreateFileMapping = modkernel32.NewProc("CreateFileMappingW") 39 | ) 40 | 41 | // GetAllocGranularity returns system allocation granularity. 42 | func GetAllocGranularity() int { 43 | var si systemInfo 44 | // this cannot fail 45 | procGetSystemInfo.Call(uintptr(unsafe.Pointer(&si))) 46 | return int(si.AllocationGranularity) 47 | } 48 | 49 | // OpenFileMapping is a wraper for windows syscall. 50 | func OpenFileMapping(access uint32, inheritHandle uint32, name string) (windows.Handle, error) { 51 | namep, err := windows.UTF16PtrFromString(name) 52 | if err != nil { 53 | return 0, err 54 | } 55 | nameu := unsafe.Pointer(namep) 56 | r1, _, err := procOpenFileMapping.Call(uintptr(access), uintptr(inheritHandle), uintptr(nameu)) 57 | allocator.Use(nameu) 58 | if r1 == 0 { 59 | if err == windows.ERROR_FILE_NOT_FOUND { 60 | return 0, &os.PathError{Path: name, Op: "CreateFileMapping", Err: err} 61 | } 62 | return 0, os.NewSyscallError("OpenFileMapping", err) 63 | } 64 | if err == syscall.Errno(0) { 65 | err = nil 66 | } 67 | return windows.Handle(r1), err 68 | } 69 | 70 | // CreateFileMapping is a wraper for windwos syscall. 71 | // We cannot use a call from golang.org/x/sys/windows, because it returns nil error, if the syscall returned a valid handle. 72 | // However, CreateFileMapping may return a valid handle along with ERROR_ALREADY_EXISTS, and in this case 73 | // we cannot find out, if the file existed before. 74 | func CreateFileMapping(fhandle windows.Handle, sa *windows.SecurityAttributes, prot uint32, maxSizeHigh uint32, maxSizeLow uint32, name string) (handle windows.Handle, err error) { 75 | var namep *uint16 76 | if len(name) > 0 { 77 | namep, err = windows.UTF16PtrFromString(name) 78 | if err != nil { 79 | return 0, err 80 | } 81 | } 82 | nameu := unsafe.Pointer(namep) 83 | sau := unsafe.Pointer(sa) 84 | r1, _, err := procCreateFileMapping.Call(uintptr(fhandle), uintptr(sau), uintptr(prot), uintptr(maxSizeHigh), uintptr(maxSizeLow), uintptr(nameu)) 85 | allocator.Use(sau) 86 | allocator.Use(nameu) 87 | if r1 == 0 { 88 | if err == windows.ERROR_ALREADY_EXISTS { 89 | return 0, &os.PathError{Path: name, Op: "CreateFileMapping", Err: err} 90 | } 91 | return 0, os.NewSyscallError("CreateFileMapping", err) 92 | } 93 | if err == syscall.Errno(0) { 94 | err = nil 95 | } 96 | return windows.Handle(r1), err 97 | } 98 | -------------------------------------------------------------------------------- /mmf/memory_region_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package mmf 4 | 5 | import ( 6 | "os" 7 | "runtime" 8 | "unsafe" 9 | 10 | "bitbucket.org/avd/go-ipc/internal/allocator" 11 | "bitbucket.org/avd/go-ipc/internal/sys/windows" 12 | 13 | "github.com/pkg/errors" 14 | "golang.org/x/sys/windows" 15 | ) 16 | 17 | func init() { 18 | g, p := sys.GetAllocGranularity(), os.Getpagesize() 19 | if g >= p { 20 | mmapOffsetMultiple = int64(g) 21 | } else { 22 | mmapOffsetMultiple = int64(p) 23 | } 24 | } 25 | 26 | type memoryRegion struct { 27 | data []byte 28 | size int 29 | pageOffset int64 30 | } 31 | 32 | type native interface { 33 | IsNative() bool 34 | } 35 | 36 | func newMemoryRegion(obj Mappable, mode int, offset int64, size int) (*memoryRegion, error) { 37 | prot, flags, err := sysProtAndFlagsFromFlag(mode) 38 | if err != nil { 39 | return nil, errors.Wrap(err, "memory region flags check failed") 40 | } 41 | if size, err = checkMmapSize(obj, size); err != nil { 42 | return nil, errors.Wrap(err, "size check failed") 43 | } 44 | maxSizeHigh := uint32((offset + int64(size)) >> 32) 45 | maxSizeLow := uint32((offset + int64(size)) & 0xFFFFFFFF) 46 | 47 | handle := windows.Handle(obj.Fd()) 48 | 49 | // check for a named region for windows native shared memory via a pagefile. 50 | // in this case there is no need to create a mapping file. 51 | if nativeObj, ok := obj.(native); !ok || !nativeObj.IsNative() { 52 | handle, err = sys.CreateFileMapping( 53 | handle, 54 | nil, 55 | prot, 56 | maxSizeHigh, 57 | maxSizeLow, 58 | "") 59 | if err != nil { 60 | return nil, errors.Wrap(err, "create mapping file failed") 61 | } 62 | defer windows.CloseHandle(handle) 63 | } 64 | 65 | pageOffset := calcMmapOffsetFixup(offset) 66 | offset -= pageOffset 67 | lowOffset := uint32(offset & 0xFFFFFFFF) 68 | highOffset := uint32(offset >> 32) 69 | 70 | addr, err := windows.MapViewOfFile(handle, flags, highOffset, lowOffset, uintptr(int64(size)+pageOffset)) 71 | if err != nil { 72 | return nil, errors.Wrap(os.NewSyscallError("MapViewOfFile", err), "failed to mmap file view") 73 | } 74 | 75 | totalSize := size + int(pageOffset) 76 | return &memoryRegion{ 77 | data: allocator.ByteSliceFromUnsafePointer(unsafe.Pointer(addr), totalSize, totalSize), 78 | size: size, 79 | pageOffset: pageOffset, 80 | }, nil 81 | } 82 | 83 | func (region *memoryRegion) Close() error { 84 | runtime.SetFinalizer(region, nil) 85 | err := windows.UnmapViewOfFile(uintptr(allocator.ByteSliceData(region.data))) 86 | if err != nil { 87 | return errors.Wrap(err, "UnmapViewOfFile failed") 88 | } 89 | region.data = nil 90 | return nil 91 | } 92 | 93 | func (region *memoryRegion) Data() []byte { 94 | return region.data[region.pageOffset:] 95 | } 96 | 97 | func (region *memoryRegion) Size() int { 98 | return region.size 99 | } 100 | 101 | func (region *memoryRegion) Flush(async bool) error { 102 | err := windows.FlushViewOfFile(uintptr(allocator.ByteSliceData(region.data)), uintptr(len(region.data))) 103 | if err != nil { 104 | return errors.Wrap(err, "FlushViewOfFile failed") 105 | } 106 | return nil 107 | } 108 | 109 | func sysProtAndFlagsFromFlag(mode int) (prot uint32, flags uint32, err error) { 110 | switch mode { 111 | case MEM_READ_ONLY: 112 | fallthrough 113 | case MEM_READ_PRIVATE: 114 | prot = windows.PAGE_READONLY 115 | flags = windows.FILE_MAP_READ 116 | case MEM_READWRITE: 117 | prot = windows.PAGE_READWRITE 118 | flags = windows.FILE_MAP_WRITE 119 | case MEM_COPY_ON_WRITE: 120 | prot = windows.PAGE_WRITECOPY 121 | flags = windows.FILE_MAP_COPY 122 | default: 123 | err = errors.Errorf("invalid mem region flags %d", mode) 124 | } 125 | return 126 | } 127 | -------------------------------------------------------------------------------- /sync/lwrwmutex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "sync/atomic" 7 | "unsafe" 8 | ) 9 | 10 | const ( 11 | lwRWMStateSize = 8 12 | lwRWMMask = 0x1FFFFF 13 | lwRWMWaitingReaderShift = 21 14 | lwRWMWriterShift = 42 15 | ) 16 | 17 | // wRWState is a shared rwmutex state with the following bits distribution: 18 | // ...63...|62.................42|41.................21|20.................0| 19 | // --------|---------------------|---------------------|--------------------| 20 | // unused | writers | waiting readers | readers | 21 | // which gives us up to 2kk readers and writers. 22 | type lwRWState int64 23 | 24 | func (s lwRWState) readers() int64 { 25 | return (int64)(s) & lwRWMMask 26 | } 27 | 28 | func (s lwRWState) waitingReaders() int64 { 29 | return ((int64)(s) >> lwRWMWaitingReaderShift) & lwRWMMask 30 | } 31 | 32 | func (s lwRWState) writers() int64 { 33 | return ((int64)(s) >> lwRWMWriterShift) & lwRWMMask 34 | } 35 | 36 | func (s *lwRWState) addReaders(count int64) { 37 | *(*int64)(s) += count 38 | } 39 | 40 | func (s *lwRWState) addWaitingReaders(count int64) { 41 | *(*int64)(s) += count << lwRWMWaitingReaderShift 42 | } 43 | 44 | func (s *lwRWState) addWriters(count int64) { 45 | *(*int64)(s) += count << lwRWMWriterShift 46 | } 47 | 48 | // lwRWMutex is an optimized low-level rwmutex implementation, 49 | // that doesn't have internal lock for its state. 50 | // this implementation is inspired by Jeff Preshing and his article at 51 | // http://preshing.com/20150316/semaphores-are-surprisingly-versatile/ 52 | // and his c++ implementation (github.com/preshing/cpp11-on-multicore). 53 | type lwRWMutex struct { 54 | rWaiter waitWaker 55 | wWaiter waitWaker 56 | state *int64 57 | } 58 | 59 | func newRWLightweightMutex(state unsafe.Pointer, rWaiter, wWaiter waitWaker) *lwRWMutex { 60 | return &lwRWMutex{state: (*int64)(state), rWaiter: rWaiter, wWaiter: wWaiter} 61 | } 62 | 63 | // init writes initial value into mutex's memory location. 64 | func (lwrw *lwRWMutex) init() { 65 | *lwrw.state = 0 66 | } 67 | 68 | func (lwrw *lwRWMutex) lock() { 69 | new := (lwRWState)(atomic.AddInt64(lwrw.state, 1< 0 || new.writers() > 1 { 71 | if err := lwrw.wWaiter.wait(0, -1); err != nil { 72 | panic(err) 73 | } 74 | } 75 | } 76 | 77 | func (lwrw *lwRWMutex) rlock() { 78 | var new lwRWState 79 | for { 80 | old := (lwRWState)(atomic.LoadInt64(lwrw.state)) 81 | new = old 82 | if new.writers() == 0 { 83 | new.addReaders(1) 84 | } else { 85 | new.addWaitingReaders(1) 86 | } 87 | if atomic.CompareAndSwapInt64(lwrw.state, (int64)(old), (int64)(new)) { 88 | break 89 | } 90 | } 91 | if new.writers() > 0 { 92 | if err := lwrw.rWaiter.wait(0, -1); err != nil { 93 | panic(err) 94 | } 95 | } 96 | } 97 | 98 | func (lwrw *lwRWMutex) runlock() { 99 | new := (lwRWState)(atomic.AddInt64(lwrw.state, -1)) 100 | if new.readers() == lwRWMMask { 101 | panic("unlock of unlocked mutex") 102 | } 103 | if new.readers() == 0 && new.writers() > 0 { 104 | lwrw.wWaiter.wake(1) 105 | } 106 | } 107 | 108 | func (lwrw *lwRWMutex) unlock() { 109 | var new lwRWState 110 | for { 111 | old := (lwRWState)(atomic.LoadInt64(lwrw.state)) 112 | if old.writers() == 0 { 113 | panic("unlock of unlocked mutex") 114 | } 115 | new = old 116 | new.addWriters(-1) 117 | if wr := new.waitingReaders(); wr > 0 { 118 | new.addWaitingReaders(-wr) 119 | new.addReaders(wr) 120 | } 121 | if atomic.CompareAndSwapInt64(lwrw.state, (int64)(old), (int64)(new)) { 122 | break 123 | } 124 | } 125 | if new.readers() > 0 { 126 | lwrw.rWaiter.wake(int32(new.readers())) 127 | } else if new.writers() > 0 { 128 | lwrw.wWaiter.wake(1) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /sync/sema_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build darwin freebsd linux 4 | 5 | package sync 6 | 7 | import ( 8 | "os" 9 | "time" 10 | 11 | "bitbucket.org/avd/go-ipc/internal/common" 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | const ( 16 | cSemUndo = 0x1000 17 | ) 18 | 19 | type sembuf struct { 20 | semnum uint16 21 | semop int16 22 | semflg int16 23 | } 24 | 25 | // semaphore is a sysV semaphore. 26 | type semaphore struct { 27 | name string 28 | id int 29 | } 30 | 31 | // newSemaphore creates a new sysV semaphore with the given name. 32 | // It generates a key from the name, and then calls NewSemaphoreKey. 33 | func newSemaphore(name string, flag int, perm os.FileMode, initial int) (*semaphore, error) { 34 | k, err := common.KeyForName(name) 35 | if err != nil { 36 | return nil, errors.Wrap(err, "failed to generate a key for the name") 37 | } 38 | result, err := newSemaphoreKey(uint64(k), flag, perm, initial) 39 | if err != nil { 40 | return nil, err 41 | } 42 | result.name = name 43 | return result, nil 44 | } 45 | 46 | // newSemaphoreKey creates a new sysV semaphore for the given key. 47 | func newSemaphoreKey(key uint64, flag int, perm os.FileMode, initial int) (*semaphore, error) { 48 | var id int 49 | creator := func(create bool) error { 50 | var creatorErr error 51 | flags := int(perm) 52 | if create { 53 | flags |= common.IpcCreate | common.IpcExcl 54 | } 55 | id, creatorErr = semget(common.Key(key), 1, flags) 56 | return creatorErr 57 | } 58 | created, err := common.OpenOrCreate(creator, flag) 59 | if err != nil { 60 | return nil, errors.Wrap(err, "failed to open/create sysv semaphore") 61 | } 62 | result := &semaphore{id: id} 63 | if created && initial > 0 { 64 | if err = result.add(initial); err != nil { 65 | result.Destroy() 66 | return nil, errors.Wrap(err, "failed to add initial semaphore value") 67 | } 68 | } 69 | return result, nil 70 | } 71 | 72 | func (s *semaphore) signal(count int) { 73 | if err := s.add(count); err != nil { 74 | panic(err) 75 | } 76 | } 77 | 78 | func (s *semaphore) wait() { 79 | if err := s.add(-1); err != nil { 80 | panic(err) 81 | } 82 | } 83 | 84 | func (s *semaphore) waitTimeout(timeout time.Duration) bool { 85 | if timeout < 0 { 86 | s.wait() 87 | return true 88 | } 89 | return doSemaTimedWait(s.id, timeout) 90 | } 91 | 92 | func (s *semaphore) close() error { 93 | return nil 94 | } 95 | 96 | func (s *semaphore) Destroy() error { 97 | return removeSysVSemaByID(s.id, s.name) 98 | } 99 | 100 | func (s *semaphore) add(value int) error { 101 | return common.UninterruptedSyscall(func() error { return semAdd(s.id, value) }) 102 | } 103 | 104 | // destroySemaphore permanently removes semaphore with the given name. 105 | func destroySemaphore(name string) error { 106 | k, err := common.KeyForName(name) 107 | if err != nil { 108 | return errors.Wrap(err, "failed to get a key for the name") 109 | } 110 | id, err := semget(k, 1, 0) 111 | if err != nil { 112 | if os.IsNotExist(err) { 113 | return nil 114 | } 115 | return errors.Wrap(err, "failed to get semaphore id") 116 | } 117 | return removeSysVSemaByID(id, name) 118 | } 119 | 120 | func removeSysVSemaByID(id int, name string) error { 121 | err := semctl(id, 0, common.IpcRmid) 122 | if err == nil && len(name) > 0 { 123 | if err = os.Remove(common.TmpFilename(name)); os.IsNotExist(err) { 124 | err = nil 125 | } else if err != nil { 126 | err = errors.Wrap(err, "failed to remove temporary file") 127 | } 128 | } else if os.IsNotExist(err) { 129 | err = nil 130 | } else { 131 | err = errors.Wrap(err, "semctl failed") 132 | } 133 | return err 134 | } 135 | 136 | func semAdd(id, value int) error { 137 | b := sembuf{semnum: 0, semop: int16(value), semflg: 0} 138 | return semop(id, []sembuf{b}) 139 | } 140 | -------------------------------------------------------------------------------- /internal/allocator/object_allocator_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package allocator 4 | 5 | import ( 6 | "sync" 7 | "testing" 8 | "unsafe" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestCheckObjectType(t *testing.T) { 14 | type validStruct struct { 15 | a, b int 16 | u uintptr 17 | s struct { 18 | arr [3]int 19 | } 20 | } 21 | type invalidStruct1 struct { 22 | a, b *int 23 | } 24 | type invalidStruct2 struct { 25 | a, b []int 26 | } 27 | type invalidStruct3 struct { 28 | s string 29 | } 30 | var i int 31 | var c complex128 32 | var arr = [3]int{} 33 | var arr2 = [3]string{} 34 | var slsl [][]int 35 | var m map[int]int 36 | 37 | assert.NoError(t, CheckObjectReferences(i)) 38 | assert.NoError(t, CheckObjectReferences(c)) 39 | assert.NoError(t, CheckObjectReferences(arr)) 40 | assert.NoError(t, CheckObjectReferences(arr[:])) 41 | assert.NoError(t, CheckObjectReferences(validStruct{})) 42 | assert.NoError(t, CheckObjectReferences(sync.Mutex{})) 43 | 44 | assert.Error(t, CheckObjectReferences(invalidStruct1{})) 45 | assert.Error(t, CheckObjectReferences(invalidStruct2{})) 46 | assert.Error(t, CheckObjectReferences(invalidStruct3{})) 47 | assert.Error(t, CheckObjectReferences(arr2)) 48 | assert.Error(t, CheckObjectReferences(arr2[:])) 49 | assert.Error(t, CheckObjectReferences(m)) 50 | assert.Error(t, CheckObjectReferences(slsl)) 51 | } 52 | 53 | func TestAllocInt(t *testing.T) { 54 | var i = 0x01027FFF 55 | data := make([]byte, unsafe.Sizeof(i)) 56 | if !assert.NoError(t, Alloc(data, i)) { 57 | return 58 | } 59 | ptr := (*int)(unsafe.Pointer(&data[0])) 60 | assert.Equal(t, i, *ptr) 61 | } 62 | 63 | func TestAllocIntArray(t *testing.T) { 64 | i := [3]int{0x01, 0x7F, 0xFF} 65 | data := make([]byte, unsafe.Sizeof(i)) 66 | if !assert.NoError(t, Alloc(data, i)) { 67 | return 68 | } 69 | ptr := (*[3]int)(unsafe.Pointer(&data[0])) 70 | assert.Equal(t, i, *ptr) 71 | } 72 | 73 | func TestAllocStruct(t *testing.T) { 74 | type internal struct { 75 | d complex128 76 | p uintptr 77 | } 78 | type s struct { 79 | a, b int 80 | ss internal 81 | } 82 | obj := s{-1, 11, internal{complex(10, 11), uintptr(0)}} 83 | data := make([]byte, unsafe.Sizeof(obj)) 84 | if !assert.NoError(t, Alloc(data, obj)) { 85 | return 86 | } 87 | ptr := (*s)(unsafe.Pointer(&data[0])) 88 | assert.Equal(t, obj, *ptr) 89 | } 90 | 91 | func TestAllocStructPtr(t *testing.T) { 92 | type internal struct { 93 | d complex128 94 | p uintptr 95 | } 96 | type s struct { 97 | a, b int 98 | ss internal 99 | } 100 | obj := &s{-1, 11, internal{complex(10, 11), uintptr(0xDEADBEEF)}} 101 | data := make([]byte, unsafe.Sizeof(*obj)) 102 | if !assert.NoError(t, Alloc(data, obj)) { 103 | return 104 | } 105 | ptr := (*s)(unsafe.Pointer(&data[0])) 106 | assert.Equal(t, obj, ptr) 107 | } 108 | 109 | func TestAllocSlice(t *testing.T) { 110 | obj := make([]int, 10) 111 | for i := range obj { 112 | obj[i] = int(i) 113 | } 114 | data := make([]byte, unsafe.Sizeof(int(0))*10) 115 | if !assert.NoError(t, Alloc(data, obj)) { 116 | return 117 | } 118 | sl := ByteSliceTointSlice(data, 10, 10) 119 | assert.Equal(t, obj, sl) 120 | } 121 | 122 | func TestAllocSliceReadAsArray(t *testing.T) { 123 | obj := make([]int, 10) 124 | for i := range obj { 125 | obj[i] = int(i) 126 | } 127 | data := make([]byte, unsafe.Sizeof(int(0))*10) 128 | if !assert.NoError(t, Alloc(data, obj)) { 129 | return 130 | } 131 | ptr := (*[10]int)(unsafe.Pointer(&data[0])) 132 | assert.Equal(t, obj, (*ptr)[:]) 133 | } 134 | 135 | func TestAllocArrayReadAsSlice(t *testing.T) { 136 | i := [3]int{0x01, 0x7F, 0xFF} 137 | data := make([]byte, unsafe.Sizeof(i)) 138 | if !assert.NoError(t, Alloc(data, i)) { 139 | return 140 | } 141 | sl := ByteSliceTointSlice(data, 3, 3) 142 | assert.Equal(t, i[:], sl) 143 | } 144 | -------------------------------------------------------------------------------- /mq/mq.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package mq 4 | 5 | import ( 6 | "io" 7 | "os" 8 | "time" 9 | 10 | "bitbucket.org/avd/go-ipc/internal/common" 11 | ) 12 | 13 | const ( 14 | // O_NONBLOCK flag makes mq send/receive operations non-blocking. 15 | O_NONBLOCK = common.O_NONBLOCK 16 | ) 17 | 18 | // Blocker is an object, which can work in blocking and non-blocking modes. 19 | type Blocker interface { 20 | SetBlocking(bool) error 21 | } 22 | 23 | // Buffered is an object with internal buffer of the given capacity. 24 | type Buffered interface { 25 | Cap() int 26 | } 27 | 28 | // Messenger is an interface which must be satisfied by any 29 | // message queue implementation on any platform. 30 | type Messenger interface { 31 | // Send sends the data. It blocks if there are no readers and the queue is full 32 | Send(data []byte) error 33 | // Receive reads data from the queue. It blocks if the queue is empty. 34 | // Returns message len. 35 | Receive(data []byte) (int, error) 36 | io.Closer 37 | } 38 | 39 | // TimedMessenger is a Messenger, which supports send/receive timeouts. 40 | // Passing 0 as a timeout makes a call non-blocking. 41 | // Passing negative value as a timeout makes the timeout infinite. 42 | type TimedMessenger interface { 43 | Messenger 44 | // SendTimeout sends the data. It blocks if there are no readers and the queue if full. 45 | // It waits for not more, than timeout. 46 | SendTimeout(data []byte, timeout time.Duration) error 47 | // ReceiveTimeout reads data from the queue. It blocks if the queue is empty. 48 | // It waits for not more, than timeout. Returns message len. 49 | ReceiveTimeout(data []byte, timeout time.Duration) (int, error) 50 | } 51 | 52 | // PriorityMessenger is a Messenger, which orders messages according to their priority. 53 | // Semantic is similar to linux native mq: 54 | // Messages are placed on the queue in decreasing order of priority, with newer messages of the same 55 | // priority being placed after older messages with the same priority. 56 | type PriorityMessenger interface { 57 | Messenger 58 | Buffered 59 | // SendPriority sends the data. The message will be inserted in the mq according to its priority. 60 | SendPriority(data []byte, prio int) error 61 | // ReceivePriority reads a message and returns its len and priority. 62 | ReceivePriority(data []byte) (int, int, error) 63 | } 64 | 65 | // New creates a mq with a given name and permissions. 66 | // It uses the default implementation. If there are several implementations on a platform, 67 | // you can use explicit create functions. 68 | // name - unique queue name. 69 | // flag - create flags. You can specify: 70 | // os.O_EXCL if you don't want to open a queue if it exists. 71 | // O_NONBLOCK if you don't want to block on send/receive. 72 | // This flag may not be supported by a particular implementation. To be sure, you can convert Messenger 73 | // to Blocker and call SetBlocking to set/unset non-blocking mode. 74 | // perm - permissions for the new queue. 75 | func New(name string, flag int, perm os.FileMode) (Messenger, error) { 76 | return createMQ(name, flag, perm) 77 | } 78 | 79 | // Open opens a mq with a given name and flags. 80 | // It uses the default implementation. If there are several implementations on a platform, 81 | // you can use explicit create functions. 82 | // name - unique queue name. 83 | // flags - 0 or O_NONBLOCK. 84 | func Open(name string, flags int) (Messenger, error) { 85 | return openMQ(name, flags) 86 | } 87 | 88 | // Destroy permanently removes mq object. 89 | func Destroy(name string) error { 90 | return destroyMq(name) 91 | } 92 | 93 | func checkMqPerm(perm os.FileMode) bool { 94 | return uint(perm)&0111 == 0 95 | } 96 | 97 | // IsTemporary returns true, if an error is a timeout error. 98 | func IsTemporary(err error) bool { 99 | return common.IsTimeoutErr(err) || isTemporaryError(err) 100 | } 101 | -------------------------------------------------------------------------------- /fifo/fifo_sys_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package fifo 4 | 5 | import ( 6 | "os" 7 | "syscall" 8 | "unsafe" 9 | 10 | "bitbucket.org/avd/go-ipc/internal/allocator" 11 | 12 | "golang.org/x/sys/windows" 13 | ) 14 | 15 | const ( 16 | cPIPE_ACCESS_DUPLEX = 0x00000003 17 | cPIPE_TYPE_MESSAGE = 0x00000004 18 | cPIPE_READMODE_MESSAGE = 0x00000002 19 | cPIPE_WAIT = 0x00000000 20 | cPIPE_NOWAIT = 0x00000001 21 | cPIPE_UNLIMITED_INSTANCES = 255 22 | cFifoBufferSize = 512 23 | cERROR_PIPE_CONNECTED = 535 24 | cERROR_PIPE_BUSY = 231 25 | cERROR_NO_DATA = 232 26 | cNMPWAIT_USE_DEFAULT_WAIT = 0x00000000 27 | cNMPWAIT_WAIT_FOREVER = 0xffffffff 28 | ) 29 | 30 | var ( 31 | modkernel32 = windows.NewLazyDLL("kernel32.dll") 32 | procCreateNamedPipe = modkernel32.NewProc("CreateNamedPipeW") 33 | procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe") 34 | procWaitNamedPipe = modkernel32.NewProc("WaitNamedPipeW") 35 | procSetNamedPipeHandleState = modkernel32.NewProc("SetNamedPipeHandleState") 36 | procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe") 37 | ) 38 | 39 | func createNamedPipe(name string, openMode, pipeMode, maxInstances, outBufferSize, inBufferSize, defaultTimeout uint32, 40 | attrs *windows.SecurityAttributes) (windows.Handle, error) { 41 | namep, err := windows.UTF16PtrFromString(name) 42 | if err != nil { 43 | return windows.InvalidHandle, err 44 | } 45 | h, _, err := procCreateNamedPipe.Call( 46 | uintptr(unsafe.Pointer(namep)), 47 | uintptr(openMode), 48 | uintptr(pipeMode), 49 | uintptr(maxInstances), 50 | uintptr(outBufferSize), 51 | uintptr(inBufferSize), 52 | uintptr(defaultTimeout), 53 | uintptr(unsafe.Pointer(attrs))) 54 | allocator.Use(unsafe.Pointer(namep)) 55 | allocator.Use(unsafe.Pointer(attrs)) 56 | handle := windows.Handle(h) 57 | if handle == windows.InvalidHandle { 58 | return handle, os.NewSyscallError("CreateNamedPipe", err) 59 | } 60 | return handle, nil 61 | } 62 | 63 | func connectNamedPipe(handle windows.Handle, over *windows.Overlapped) (bool, error) { 64 | overPtr := unsafe.Pointer(over) 65 | r1, _, err := procConnectNamedPipe.Call(uintptr(handle), uintptr(overPtr)) 66 | allocator.Use(overPtr) 67 | if err != syscall.Errno(0) { 68 | return false, os.NewSyscallError("ConnectNamedPipe", err) 69 | } 70 | return r1 != 0, nil 71 | } 72 | 73 | func disconnectNamedPipe(handle windows.Handle) (bool, error) { 74 | r1, _, err := procDisconnectNamedPipe.Call(uintptr(handle)) 75 | if err != syscall.Errno(0) { 76 | return false, os.NewSyscallError("DisconnectNamedPipe", err) 77 | } 78 | return r1 != 0, nil 79 | } 80 | 81 | func waitNamedPipe(name string, timeout uint32) (bool, error) { 82 | namep, err := windows.UTF16PtrFromString(name) 83 | if err != nil { 84 | return false, err 85 | } 86 | uNamep := unsafe.Pointer(namep) 87 | r1, _, err := procWaitNamedPipe.Call(uintptr(uNamep), uintptr(timeout)) 88 | allocator.Use(uNamep) 89 | if err != syscall.Errno(0) { 90 | return false, os.NewSyscallError("WaitNamedPipe", err) 91 | } 92 | return r1 != 0, nil 93 | } 94 | 95 | func setNamedPipeHandleState(h windows.Handle, mode, maxCollectionCount, collectDataTimeout *uint32) (bool, error) { 96 | pMode := unsafe.Pointer(mode) 97 | pMaxCollectionCount, pCollectDataTimeout := unsafe.Pointer(maxCollectionCount), unsafe.Pointer(collectDataTimeout) 98 | r1, _, err := procSetNamedPipeHandleState.Call( 99 | uintptr(h), 100 | uintptr(pMode), 101 | uintptr(pMaxCollectionCount), 102 | uintptr(pCollectDataTimeout)) 103 | allocator.Use(pMode) 104 | allocator.Use(pMaxCollectionCount) 105 | allocator.Use(pCollectDataTimeout) 106 | if err != syscall.Errno(0) { 107 | return false, os.NewSyscallError("SetNamedPipeHandleState", err) 108 | } 109 | return r1 != 0, nil 110 | } 111 | -------------------------------------------------------------------------------- /sync/mutex_ev_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package sync 4 | 5 | import ( 6 | "os" 7 | "time" 8 | 9 | "bitbucket.org/avd/go-ipc/internal/allocator" 10 | "bitbucket.org/avd/go-ipc/internal/common" 11 | "bitbucket.org/avd/go-ipc/internal/helper" 12 | "bitbucket.org/avd/go-ipc/mmf" 13 | "bitbucket.org/avd/go-ipc/shm" 14 | 15 | "github.com/pkg/errors" 16 | "golang.org/x/sys/windows" 17 | ) 18 | 19 | // all implementations must satisfy IPCLocker interface. 20 | var ( 21 | _ IPCLocker = (*EventMutex)(nil) 22 | timeoutErr = common.NewTimeoutError("WaitForSingleObject") 23 | ) 24 | 25 | // EventMutex is a mutex built on named windows events. 26 | // It is not possible to use native windows named mutex, because 27 | // goroutines migrate between threads, and windows mutex must 28 | // be released by the same thread it was locked. 29 | type EventMutex struct { 30 | handle windows.Handle 31 | state *mmf.MemoryRegion 32 | name string 33 | lwm *lwMutex 34 | } 35 | 36 | // NewEventMutex creates a new event-basedmutex. 37 | // name - object name. 38 | // flag - flag is a combination of open flags from 'os' package. 39 | // perm - object's permission bits. 40 | func NewEventMutex(name string, flag int, perm os.FileMode) (*EventMutex, error) { 41 | if err := ensureOpenFlags(flag); err != nil { 42 | return nil, err 43 | } 44 | region, created, err := helper.CreateWritableRegion(mutexSharedStateName(name, "e"), flag, perm, lwmStateSize) 45 | if err != nil { 46 | return nil, errors.Wrap(err, "failed to create shared state") 47 | } 48 | handle, err := openOrCreateEvent(name, flag, 1) 49 | if err != nil { 50 | region.Close() 51 | if created { 52 | shm.DestroyMemoryObject(mutexSharedStateName(name, "e")) 53 | } 54 | return nil, errors.Wrap(err, "failed to open/create event mutex") 55 | } 56 | result := &EventMutex{ 57 | handle: handle, 58 | state: region, 59 | name: name, 60 | lwm: newLightweightMutex(allocator.ByteSliceData(region.Data()), &eventWaiter{handle: handle}), 61 | } 62 | if created { 63 | result.lwm.init() 64 | } 65 | return result, nil 66 | } 67 | 68 | // Lock locks the mutex. It panics on an error. 69 | func (m *EventMutex) Lock() { 70 | m.lwm.lock() 71 | } 72 | 73 | // TryLock makes one attempt to lock the mutex. It return true on succeess and false otherwise. 74 | func (m *EventMutex) TryLock() bool { 75 | return m.lwm.tryLock() 76 | } 77 | 78 | // LockTimeout tries to lock the locker, waiting for not more, than timeout. 79 | func (m *EventMutex) LockTimeout(timeout time.Duration) bool { 80 | return m.lwm.lockTimeout(timeout) 81 | } 82 | 83 | // Unlock releases the mutex. It panics on an error. 84 | func (m *EventMutex) Unlock() { 85 | m.lwm.unlock() 86 | } 87 | 88 | // Close closes event's handle. 89 | func (m *EventMutex) Close() error { 90 | m.state.Close() 91 | return windows.CloseHandle(m.handle) 92 | } 93 | 94 | // DestroyEventMutex destroys shared mutex state. 95 | // The event object is destroyed, when its last handle is closed. 96 | func DestroyEventMutex(name string) error { 97 | return shm.DestroyMemoryObject(mutexSharedStateName(name, "e")) 98 | } 99 | 100 | type eventWaiter struct { 101 | handle windows.Handle 102 | } 103 | 104 | func (e *eventWaiter) wake(int32) (int, error) { 105 | if err := windows.SetEvent(e.handle); err != nil { 106 | panic("failed to unlock mutex: " + err.Error()) 107 | } 108 | return 1, nil 109 | } 110 | 111 | func (e *eventWaiter) wait(unused int32, timeout time.Duration) error { 112 | waitMillis := uint32(windows.INFINITE) 113 | if timeout >= 0 { 114 | waitMillis = uint32(timeout.Nanoseconds() / 1e6) 115 | } 116 | ev, err := windows.WaitForSingleObject(e.handle, waitMillis) 117 | switch ev { 118 | case windows.WAIT_OBJECT_0: 119 | return nil 120 | case uint32(windows.WAIT_TIMEOUT): 121 | return timeoutErr 122 | default: 123 | if err != nil { 124 | panic(err) 125 | } else { 126 | panic(errors.Errorf("invalid wait state for a mutex: %d", ev)) 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /shm/shared_memory.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package shm 4 | 5 | import ( 6 | "os" 7 | "runtime" 8 | 9 | "bitbucket.org/avd/go-ipc/internal/common" 10 | "bitbucket.org/avd/go-ipc/mmf" 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | var ( 15 | _ SharedMemoryObject = (*MemoryObject)(nil) 16 | ) 17 | 18 | // SharedMemoryObject is an interface, which must be implemented 19 | // by any implemetation of an object used for mapping into memory. 20 | type SharedMemoryObject interface { 21 | Size() int64 22 | Truncate(size int64) error 23 | Close() error 24 | Destroy() error 25 | mmf.Mappable 26 | } 27 | 28 | // MemoryObject represents an object which can be used to 29 | // map shared memory regions into the process' address space. 30 | type MemoryObject struct { 31 | *memoryObject 32 | } 33 | 34 | // NewMemoryObject creates a new shared memory object. 35 | // name - a name of the object. should not contain '/' and exceed 255 symbols (30 on darwin). 36 | // size - object size. 37 | // flag - flag is a combination of open flags from 'os' package. 38 | // perm - object's permission bits. 39 | func NewMemoryObject(name string, flag int, perm os.FileMode) (*MemoryObject, error) { 40 | impl, err := newMemoryObject(name, flag, perm) 41 | if err != nil { 42 | return nil, err 43 | } 44 | result := &MemoryObject{impl} 45 | runtime.SetFinalizer(impl, func(memObject *memoryObject) { 46 | memObject.Close() 47 | }) 48 | return result, nil 49 | } 50 | 51 | // NewMemoryObjectSize opens or creates a shared memory object with the given name. 52 | // If the object was created, it is truncated to 'size'. 53 | // Otherwise, checks, that the existing object is at least 'size' bytes long. 54 | // Returns an object, true, if it was created, and an error. 55 | func NewMemoryObjectSize(name string, flag int, perm os.FileMode, size int64) (SharedMemoryObject, bool, error) { 56 | var obj *MemoryObject 57 | creator := func(create bool) error { 58 | var err error 59 | creatorFlag := os.O_RDWR 60 | if create { 61 | creatorFlag |= (os.O_CREATE | os.O_EXCL) 62 | } 63 | obj, err = NewMemoryObject(name, creatorFlag, perm) 64 | return errors.Cause(err) 65 | } 66 | created, resultErr := common.OpenOrCreate(creator, flag) 67 | if resultErr != nil { 68 | return nil, false, resultErr 69 | } 70 | if created { 71 | if resultErr = obj.Truncate(size); resultErr != nil { 72 | return nil, false, resultErr 73 | } 74 | } else if obj.Size() < size { 75 | return nil, false, errors.Errorf("existing object is smaller (%d), than needed(%d)", obj.Size(), size) 76 | } 77 | return obj, created, nil 78 | } 79 | 80 | // Destroy closes the object and removes it permanently. 81 | func (obj *MemoryObject) Destroy() error { 82 | return obj.memoryObject.Destroy() 83 | } 84 | 85 | // Name returns the name of the object as it was given to NewMemoryObject(). 86 | func (obj *MemoryObject) Name() string { 87 | return obj.memoryObject.Name() 88 | } 89 | 90 | // Close closes object's underlying file object. 91 | // Darwin: a call to Close() causes invalid argument error, 92 | // if the object was not truncated. So, in this case we return nil as an error. 93 | func (obj *MemoryObject) Close() error { 94 | return obj.memoryObject.Close() 95 | } 96 | 97 | // Truncate resizes the shared memory object. 98 | // Darwin: it is possible to truncate an object only once after it was created. 99 | // Darwin: the size should be divisible by system page size, 100 | // otherwise the size is set to the nearest page size divider greater, then the given size. 101 | func (obj *MemoryObject) Truncate(size int64) error { 102 | return obj.memoryObject.Truncate(size) 103 | } 104 | 105 | // Size returns the current object size. 106 | // Darwin: it may differ from the size passed passed to Truncate. 107 | func (obj *MemoryObject) Size() int64 { 108 | return obj.memoryObject.Size() 109 | } 110 | 111 | // Fd returns a descriptor of the object's underlying file object. 112 | func (obj *MemoryObject) Fd() uintptr { 113 | return obj.memoryObject.Fd() 114 | } 115 | 116 | // DestroyMemoryObject permanently removes given memory object. 117 | func DestroyMemoryObject(name string) error { 118 | return destroyMemoryObject(name) 119 | } 120 | -------------------------------------------------------------------------------- /mq/mq_prio_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package mq 4 | 5 | import ( 6 | "math/rand" 7 | "os" 8 | "sort" 9 | "sync" 10 | "sync/atomic" 11 | "testing" 12 | "time" 13 | 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | type prioMqCtor func(name string, flag int, perm os.FileMode, maxQueueSize, maxMsgSize int) (PriorityMessenger, error) 18 | type prioMqOpener func(name string, flag int) (PriorityMessenger, error) 19 | 20 | func testPrioMq1(t *testing.T, ctor prioMqCtor, opener prioMqOpener, dtor mqDtor) { 21 | prios := [...]int{8, 4, 7, 1, 0, 15, 2, 4} 22 | a := assert.New(t) 23 | if dtor != nil { 24 | a.NoError(dtor(testMqName)) 25 | } 26 | mq, err := ctor(testMqName, O_NONBLOCK, 0666, len(prios), 8) 27 | if !a.NoError(err) { 28 | return 29 | } 30 | defer func() { 31 | a.NoError(mq.Close()) 32 | if dtor != nil { 33 | a.NoError(dtor(testMqName)) 34 | } 35 | }() 36 | message := make([]byte, 8) 37 | for _, prio := range prios { 38 | message[0] = byte(prio) 39 | if !a.NoError(mq.SendPriority(message, prio)) { 40 | return 41 | } 42 | } 43 | sort.Ints(prios[:]) 44 | for i := len(prios) - 1; i >= 0; i-- { 45 | message := make([]byte, 8) 46 | l, prio, err := mq.ReceivePriority(message) 47 | if !a.NoError(err) { 48 | continue 49 | } else { 50 | a.Equal(len(message), l) 51 | a.Equal(prios[i], prio, "error at %d", i) 52 | a.Equal(byte(prio), message[0]) 53 | } 54 | } 55 | } 56 | 57 | type prioBenchmarkParams struct { 58 | readers int 59 | writers int 60 | mqSize int 61 | msgSize int 62 | flag int 63 | } 64 | 65 | func benchmarkPrioMq1(b *testing.B, ctor prioMqCtor, opener prioMqOpener, dtor mqDtor, params *prioBenchmarkParams) { 66 | if dtor != nil { 67 | dtor(testMqName) 68 | } 69 | mq, err := ctor(testMqName, params.flag|O_NONBLOCK, 0666, params.mqSize, params.msgSize) 70 | if err != nil { 71 | b.Error(err) 72 | return 73 | } 74 | defer func() { 75 | mq.Close() 76 | if dtor != nil { 77 | dtor(testMqName) 78 | } 79 | }() 80 | var wgw, wgr sync.WaitGroup 81 | wgw.Add(params.writers) 82 | wgr.Add(params.readers) 83 | var sent, received, done int32 84 | for i := 0; i < params.writers; i++ { 85 | go func() { 86 | defer wgw.Done() 87 | inst, err := opener(testMqName, params.flag) 88 | if err != nil { 89 | b.Error(err) 90 | return 91 | } 92 | defer inst.Close() 93 | rnd := rand.New(rand.NewSource(time.Now().UnixNano())) 94 | mess := make([]byte, params.msgSize) 95 | for j := 0; j < b.N; j++ { 96 | mess[0] = byte(rnd.Intn(100)) 97 | if err := inst.SendPriority(mess, int(mess[0])); err != nil { 98 | if !(params.flag&O_NONBLOCK != 0 && IsTemporary(err)) { 99 | b.Error(err) 100 | } 101 | } else { 102 | atomic.AddInt32(&sent, 1) 103 | } 104 | } 105 | }() 106 | } 107 | for i := 0; i < params.readers; i++ { 108 | go func() { 109 | defer wgr.Done() 110 | inst, err := opener(testMqName, params.flag) 111 | if err != nil { 112 | b.Error(err) 113 | return 114 | } 115 | defer inst.Close() 116 | mess := make([]byte, params.msgSize) 117 | for j := 0; j < b.N; j++ { 118 | if l, prio, err := inst.ReceivePriority(mess); err != nil { 119 | if !(params.flag&O_NONBLOCK != 0 && IsTemporary(err)) { 120 | b.Error(err) 121 | } 122 | } else if prio == int(mess[0]) { 123 | if l != params.msgSize { 124 | b.Errorf("error in msg len: %d != %d", params.msgSize, l) 125 | } else { 126 | atomic.AddInt32(&received, 1) 127 | } 128 | } else { 129 | b.Errorf("error in prio: %d != %d", prio, int(mess[0])) 130 | } 131 | } 132 | }() 133 | } 134 | wgw.Wait() 135 | atomic.StoreInt32(&done, 1) 136 | if params.flag&O_NONBLOCK == 0 { // wake up all readers 137 | mess := make([]byte, params.msgSize) 138 | for i := 0; i < params.readers; i++ { 139 | if err := mq.SendPriority(mess, 0); err != nil && !IsTemporary(err) { 140 | b.Error(err) 141 | } else { 142 | atomic.AddInt32(&sent, 1) 143 | } 144 | } 145 | } 146 | wgr.Wait() 147 | if sent > 0 { 148 | b.Logf("%d of %d (%2.2f%%) messages received for N = %d", received, sent, float64(received)/float64(sent)*100, b.N) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /mmf/mmf_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package mmf 4 | 5 | import ( 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | const ( 15 | testFolder = "./testdata/" 16 | testFile = testFolder + "test.bin" 17 | ) 18 | 19 | func TestMmfOpen(t *testing.T) { 20 | a := assert.New(t) 21 | file, err := os.Open(testFile) 22 | if !assert.NoError(t, err) { 23 | return 24 | } 25 | defer file.Close() 26 | stat, err := file.Stat() 27 | if !a.NoError(err) { 28 | return 29 | } 30 | mr, err := NewMemoryRegion(file, MEM_READ_ONLY, 0, int(stat.Size())) 31 | if !a.NoError(err) { 32 | return 33 | } 34 | a.NoError(mr.Close()) 35 | mr, err = NewMemoryRegion(file, MEM_READ_ONLY, 0, 0) 36 | a.NoError(err) 37 | a.NoError(mr.Close()) 38 | mr, err = NewMemoryRegion(file, MEM_READ_ONLY, 67746, int(stat.Size())-67746) 39 | a.NoError(err) 40 | a.NoError(mr.Close()) 41 | mr, err = NewMemoryRegion(file, MEM_READ_ONLY, stat.Size()-1024, 1024) 42 | a.NoError(err) 43 | a.NoError(mr.Close()) 44 | _, err = NewMemoryRegion(file, MEM_READ_ONLY, stat.Size()-1024, 1025) 45 | a.Error(err) 46 | } 47 | 48 | func TestMmfOpenReadonly(t *testing.T) { 49 | const ( 50 | offset = 67746 51 | ) 52 | file, err := os.Open(testFile) 53 | if !assert.NoError(t, err) { 54 | return 55 | } 56 | defer file.Close() 57 | region, err := NewMemoryRegion(file, MEM_READ_ONLY, offset, 1024) 58 | if !assert.NoError(t, err) { 59 | return 60 | } 61 | assert.Equal(t, 1024, region.Size()) 62 | for i := 0; i < 1024; i++ { 63 | if !assert.Equal(t, byte(i+offset), region.Data()[i]) { 64 | break 65 | } 66 | } 67 | region.Close() 68 | } 69 | 70 | func TestMmfFileCopy(t *testing.T) { 71 | a := assert.New(t) 72 | inFile, err := os.Open(testFile) 73 | if !assert.NoError(t, err) { 74 | return 75 | } 76 | defer inFile.Close() 77 | stat, err := inFile.Stat() 78 | if !a.NoError(err) { 79 | return 80 | } 81 | outFile, err := os.Create(os.TempDir() + "/tmp.bin") 82 | if !a.NoError(err) { 83 | return 84 | } 85 | defer func() { 86 | a.NoError(outFile.Close()) 87 | a.NoError(os.Remove(os.TempDir() + "/tmp.bin")) 88 | }() 89 | if !a.NoError(outFile.Truncate(stat.Size())) { 90 | return 91 | } 92 | inRegion, err := NewMemoryRegion(inFile, MEM_READ_ONLY, 0, 0) 93 | if !a.NoError(err) { 94 | return 95 | } 96 | defer func() { 97 | a.NoError(inRegion.Close()) 98 | }() 99 | outRegion, err := NewMemoryRegion(outFile, MEM_READWRITE, 0, 0) 100 | if !a.NoError(err) { 101 | return 102 | } 103 | defer func() { 104 | a.NoError(outRegion.Close()) 105 | }() 106 | rd := NewMemoryRegionReader(inRegion) 107 | wr := NewMemoryRegionWriter(outRegion) 108 | written, err := io.Copy(wr, rd) 109 | a.Equal(written, stat.Size()) 110 | a.NoError(err) 111 | if !a.NoError(outRegion.Flush(false)) { 112 | return 113 | } 114 | expected, err := ioutil.ReadAll(inFile) 115 | if !a.NoError(err) { 116 | return 117 | } 118 | actual, err := ioutil.ReadAll(outFile) 119 | if !a.NoError(err) { 120 | return 121 | } 122 | a.Equal(expected, actual) 123 | } 124 | 125 | func ExampleMemoryRegion() { 126 | // this example shows how to copy a file using mmf. 127 | 128 | // open source file for reading. 129 | inFile, err := os.Open("in.dat") 130 | if err != nil { 131 | panic("open file") 132 | } 133 | stat, err := inFile.Stat() 134 | if err != nil { 135 | panic("stat") 136 | } 137 | // open destination file for writing. 138 | outFile, err := os.Create("out.dat") 139 | if err != nil { 140 | panic("create file") 141 | } 142 | // then, mmap contents of both files. 143 | inRegion, err := NewMemoryRegion(inFile, MEM_READ_ONLY, 0, 0) 144 | if err != nil { 145 | panic("in region") 146 | } 147 | defer inRegion.Close() 148 | outRegion, err := NewMemoryRegion(outFile, MEM_READWRITE, 0, 0) 149 | if err != nil { 150 | panic("out region") 151 | } 152 | defer outRegion.Close() 153 | 154 | // copy file contents. 155 | rd := NewMemoryRegionReader(inRegion) 156 | wr := NewMemoryRegionWriter(outRegion) 157 | written, err := io.Copy(wr, rd) 158 | 159 | if err != nil || written != stat.Size() { 160 | panic("copy") 161 | } 162 | 163 | // ensure the data has been written. 164 | if err := outRegion.Flush(false); err != nil { 165 | panic("flush") 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /mq/internal/test/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "os" 9 | "strconv" 10 | "strings" 11 | "time" 12 | 13 | "bitbucket.org/avd/go-ipc/internal/test" 14 | "bitbucket.org/avd/go-ipc/mq" 15 | ) 16 | 17 | var ( 18 | objName = flag.String("object", "", "mq name") 19 | timeout = flag.Int("timeout", -1, "timeout for send/receive/notify wait. in ms.") 20 | typ = flag.String("type", "default", "message queue type") 21 | options = flag.String("options", "", "a set of options for a particular mq") 22 | ) 23 | 24 | const usage = ` test program for message queues. 25 | available commands: 26 | create 27 | destroy 28 | test {expected values byte array} 29 | send {values byte array} 30 | notifywait 31 | byte array should be passed as a continuous string of 2-symbol hex byte values like '01020A' 32 | ` 33 | 34 | func create() error { 35 | if flag.NArg() != 1 { 36 | return fmt.Errorf("create: must provide exactly one argument") 37 | } 38 | mq, err := createMqWithType(*objName, 0666, *typ, *options) 39 | if err == nil { 40 | mq.Close() 41 | } 42 | return err 43 | } 44 | 45 | func destroy() error { 46 | if flag.NArg() != 1 { 47 | return fmt.Errorf("destroy: must not provide any arguments") 48 | } 49 | return destroyMqWithType(*objName, *typ) 50 | } 51 | 52 | func test() error { 53 | if flag.NArg() != 2 { 54 | return fmt.Errorf("test: must provide exactly one argument") 55 | } 56 | msqQueue, err := openMqWithType(*objName, os.O_RDONLY, *typ) 57 | if err != nil { 58 | return err 59 | } 60 | defer msqQueue.Close() 61 | expected, err := testutil.StringToBytes(flag.Arg(1)) 62 | if err != nil { 63 | return err 64 | } 65 | received := make([]byte, len(expected)) 66 | var l int 67 | if *timeout >= 0 { 68 | if tm, ok := msqQueue.(mq.TimedMessenger); ok { 69 | l, err = tm.ReceiveTimeout(received, time.Duration(*timeout)*time.Millisecond) 70 | } else { 71 | return fmt.Errorf("selected mq implementation does not support timeouts") 72 | } 73 | } else { 74 | l, err = msqQueue.Receive(received) 75 | } 76 | if err != nil { 77 | return err 78 | } 79 | if l != len(expected) { 80 | return fmt.Errorf("invalid len. expected '%d', got '%d'", len(expected), l) 81 | } 82 | for i, expectedValue := range expected { 83 | if expectedValue != received[i] { 84 | return fmt.Errorf("invalid value at %d. expected '%d', got '%d'", i, expectedValue, received[i]) 85 | } 86 | } 87 | return nil 88 | } 89 | 90 | func send() error { 91 | if flag.NArg() != 2 { 92 | return fmt.Errorf("send: must provide exactly one argument") 93 | } 94 | msgQueue, err := openMqWithType(*objName, os.O_WRONLY, *typ) 95 | if err != nil { 96 | return err 97 | } 98 | defer msgQueue.Close() 99 | toSend, err := testutil.StringToBytes(flag.Arg(1)) 100 | if err != nil { 101 | return err 102 | } 103 | if *timeout >= 0 { 104 | if tm, ok := msgQueue.(mq.TimedMessenger); ok { 105 | err = tm.SendTimeout(toSend, time.Duration(*timeout)*time.Millisecond) 106 | } else { 107 | return fmt.Errorf("selected mq implementation does not support timeouts") 108 | } 109 | } else { 110 | err = msgQueue.Send(toSend) 111 | } 112 | return err 113 | } 114 | 115 | func runCommand() error { 116 | command := flag.Arg(0) 117 | switch command { 118 | case "create": 119 | return create() 120 | case "destroy": 121 | return destroy() 122 | case "test": 123 | return test() 124 | case "send": 125 | return send() 126 | case "notifywait": 127 | if flag.NArg() != 1 { 128 | return fmt.Errorf("notifywait: must not provide any arguments") 129 | } 130 | return notifywait(*objName, *timeout, *typ) 131 | default: 132 | return fmt.Errorf("unknown command") 133 | } 134 | } 135 | 136 | func parseTwoInts(opt string) (first int, second int, err error) { 137 | if len(opt) == 0 { 138 | return 139 | } 140 | parts := strings.Split(opt, ",") 141 | first, err = strconv.Atoi(parts[0]) 142 | if err != nil { 143 | return 144 | } 145 | if len(parts) > 1 { 146 | second, err = strconv.Atoi(parts[1]) 147 | if err != nil { 148 | return 149 | } 150 | } 151 | return 152 | } 153 | 154 | func main() { 155 | flag.Parse() 156 | if len(*objName) == 0 || flag.NArg() == 0 { 157 | fmt.Print(usage) 158 | flag.Usage() 159 | os.Exit(1) 160 | } 161 | if err := runCommand(); err != nil { 162 | fmt.Fprintf(os.Stderr, "%v\n", err) 163 | os.Exit(1) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /shm/internal/test/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "os" 9 | "strconv" 10 | 11 | "bitbucket.org/avd/go-ipc/internal/test" 12 | "bitbucket.org/avd/go-ipc/mmf" 13 | ) 14 | 15 | var ( 16 | objName = flag.String("object", "", "shared memory object name") 17 | objType = flag.String("type", "", "object type (empty for default | 'wnm' for windows native)") 18 | ) 19 | 20 | const usage = ` test program for shared memory. 21 | available commands: 22 | create {size} 23 | destroy 24 | read offset len 25 | test offset {expected values byte array} 26 | write offset {values byte array} 27 | byte array should be passed as a continuous string of 2-symbol hex byte values like '01020A' 28 | ` 29 | 30 | func create() error { 31 | if flag.NArg() != 2 { 32 | return fmt.Errorf("create: must provide exactly one argument") 33 | } 34 | size, err := strconv.Atoi(flag.Arg(1)) 35 | if err != nil { 36 | return err 37 | } 38 | obj, err := newShmObject(*objName, os.O_CREATE|os.O_RDWR, 0666, *objType, size) 39 | if err != nil { 40 | return err 41 | } 42 | return obj.Close() 43 | } 44 | 45 | func destroy() error { 46 | if flag.NArg() != 1 { 47 | return fmt.Errorf("destroy: must not provide any arguments") 48 | } 49 | return destroyShmObject(*objName, *objType) 50 | } 51 | 52 | func read() error { 53 | if flag.NArg() != 3 { 54 | return fmt.Errorf("read: must provide exactly two arguments") 55 | } 56 | offset, err := strconv.Atoi(flag.Arg(1)) 57 | if err != nil { 58 | return err 59 | } 60 | length, err := strconv.Atoi(flag.Arg(2)) 61 | if err != nil { 62 | return err 63 | } 64 | object, err := newShmObject(*objName, os.O_RDONLY, 0666, *objType, length) 65 | if err != nil { 66 | return err 67 | } 68 | defer object.Close() 69 | region, err := mmf.NewMemoryRegion(object, mmf.MEM_READ_ONLY, int64(offset), length) 70 | if err != nil { 71 | return err 72 | } 73 | defer region.Close() 74 | if len(region.Data()) > 0 { 75 | fmt.Println(testutil.BytesToString(region.Data())) 76 | } 77 | return nil 78 | } 79 | 80 | func test() error { 81 | if flag.NArg() != 3 { 82 | return fmt.Errorf("test: must provide exactly two arguments") 83 | } 84 | offset, err := strconv.Atoi(flag.Arg(1)) 85 | if err != nil { 86 | return err 87 | } 88 | data, err := testutil.StringToBytes(flag.Arg(2)) 89 | if err != nil { 90 | return err 91 | } 92 | object, err := newShmObject(*objName, os.O_RDONLY, 0666, *objType, len(data)) 93 | if err != nil { 94 | return err 95 | } 96 | defer object.Close() 97 | region, err := mmf.NewMemoryRegion(object, mmf.MEM_READ_ONLY, int64(offset), len(data)) 98 | defer region.Close() 99 | if err != nil { 100 | return err 101 | } 102 | for i, value := range region.Data() { 103 | if value != data[i] { 104 | return fmt.Errorf("invalid value at %d. expected '%d', got '%d'", i, data[i], value) 105 | } 106 | } 107 | return nil 108 | } 109 | 110 | func write() error { 111 | if flag.NArg() != 3 { 112 | return fmt.Errorf("test: must provide exactly two arguments") 113 | } 114 | offset, err := strconv.Atoi(flag.Arg(1)) 115 | if err != nil { 116 | return err 117 | } 118 | data, err := testutil.StringToBytes(flag.Arg(2)) 119 | if err != nil { 120 | return err 121 | } 122 | object, err := newShmObject(*objName, os.O_CREATE|os.O_RDWR, 0666, *objType, len(data)) 123 | if err != nil { 124 | return err 125 | } 126 | defer object.Close() 127 | region, err := mmf.NewMemoryRegion(object, mmf.MEM_READWRITE, int64(offset), len(data)) 128 | if err != nil { 129 | return err 130 | } 131 | defer func() { 132 | region.Flush(true) 133 | region.Close() 134 | }() 135 | rData := region.Data() 136 | for i, value := range data { 137 | rData[i] = value 138 | } 139 | return nil 140 | } 141 | 142 | func runCommand() error { 143 | command := flag.Arg(0) 144 | switch command { 145 | case "create": 146 | return create() 147 | case "destroy": 148 | return destroy() 149 | case "read": 150 | return read() 151 | case "test": 152 | return test() 153 | case "write": 154 | return write() 155 | default: 156 | return fmt.Errorf("unknown command") 157 | } 158 | } 159 | 160 | func main() { 161 | flag.Parse() 162 | if len(*objName) == 0 || flag.NArg() == 0 { 163 | fmt.Print(usage) 164 | flag.Usage() 165 | os.Exit(1) 166 | } 167 | if err := runCommand(); err != nil { 168 | fmt.Fprintf(os.Stderr, "%v\n", err) 169 | os.Exit(1) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /shm/shared_memory_native_windows_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package shm 4 | 5 | import ( 6 | "os" 7 | "testing" 8 | 9 | "bitbucket.org/avd/go-ipc/internal/test" 10 | "bitbucket.org/avd/go-ipc/mmf" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestCreateWindowsMemoryObject(t *testing.T) { 16 | a := assert.New(t) 17 | obj, err := NewWindowsNativeMemoryObject(defaultObjectName, os.O_RDWR, 1024) 18 | if !a.Error(err) { 19 | obj.Close() 20 | return 21 | } 22 | closer := func(obj *WindowsNativeMemoryObject) { 23 | a.NoError(obj.Close()) 24 | } 25 | obj, err = NewWindowsNativeMemoryObject(defaultObjectName, os.O_CREATE|os.O_EXCL|os.O_RDWR, 1024) 26 | if !a.NoError(err) { 27 | return 28 | } 29 | defer closer(obj) 30 | obj, err = NewWindowsNativeMemoryObject(defaultObjectName, os.O_CREATE|os.O_EXCL|os.O_RDWR, 1024) 31 | if !a.Error(err) { 32 | obj.Close() 33 | return 34 | } 35 | obj, err = NewWindowsNativeMemoryObject(defaultObjectName, os.O_RDWR, 1024) 36 | if !a.NoError(err) { 37 | return 38 | } 39 | defer closer(obj) 40 | obj, err = NewWindowsNativeMemoryObject(defaultObjectName, os.O_CREATE|os.O_RDWR, 1024) 41 | if !a.NoError(err) { 42 | return 43 | } 44 | defer closer(obj) 45 | } 46 | 47 | func TestWriteWindowsMemoryRegionSameProcess(t *testing.T) { 48 | a := assert.New(t) 49 | object, err := NewWindowsNativeMemoryObject(defaultObjectName, os.O_CREATE|os.O_RDWR, len(shmTestData)) 50 | if !a.NoError(err) { 51 | return 52 | } 53 | defer func() { 54 | a.NoError(object.Close()) 55 | }() 56 | region, err := mmf.NewMemoryRegion(object, mmf.MEM_READWRITE, 0, len(shmTestData)) 57 | if !a.NoError(err) { 58 | return 59 | } 60 | defer func() { 61 | a.NoError(region.Close()) 62 | }() 63 | copied := copy(region.Data(), shmTestData) 64 | assert.Equal(t, copied, len(shmTestData)) 65 | assert.NoError(t, region.Flush(false)) 66 | region2, err := mmf.NewMemoryRegion(object, mmf.MEM_READ_ONLY, 0, len(shmTestData)) 67 | if !a.NoError(err) { 68 | return 69 | } 70 | if !assert.NoError(t, err) { 71 | return 72 | } 73 | assert.Equal(t, region.Data(), region2.Data()) 74 | assert.NoError(t, region2.Close()) 75 | } 76 | 77 | func TestWriteWindowsMemoryAnotherProcess(t *testing.T) { 78 | a := assert.New(t) 79 | object, err := NewWindowsNativeMemoryObject(defaultObjectName, os.O_CREATE|os.O_RDWR, len(shmTestData)) 80 | if !a.NoError(err) { 81 | return 82 | } 83 | defer func() { 84 | a.NoError(object.Close()) 85 | }() 86 | region, err := mmf.NewMemoryRegion(object, mmf.MEM_READWRITE, 128, len(shmTestData)) 87 | if !a.NoError(err) { 88 | return 89 | } 90 | defer func() { 91 | a.NoError(region.Close()) 92 | }() 93 | copy(region.Data(), shmTestData) 94 | a.NoError(region.Flush(false)) 95 | result := testutil.RunTestApp(argsForShmTestCommand(defaultObjectName, "wnm", 128, shmTestData), nil) 96 | if !a.NoError(result.Err) { 97 | t.Log(result.Output) 98 | } 99 | } 100 | 101 | func TestReadWindowsMemoryAnotherProcess(t *testing.T) { 102 | a := assert.New(t) 103 | object, err := NewWindowsNativeMemoryObject(defaultObjectName, os.O_CREATE|os.O_RDWR, len(shmTestData)) 104 | if !a.NoError(err) { 105 | return 106 | } 107 | defer func() { 108 | a.NoError(object.Close()) 109 | }() 110 | region, err := mmf.NewMemoryRegion(object, mmf.MEM_READWRITE, 0, len(shmTestData)) 111 | if !a.NoError(err) { 112 | return 113 | } 114 | defer func() { 115 | a.NoError(region.Close()) 116 | }() 117 | result := testutil.RunTestApp(argsForShmWriteCommand(defaultObjectName, "wnm", 0, shmTestData), nil) 118 | if !a.NoError(result.Err) { 119 | t.Log(result.Output) 120 | return 121 | } 122 | a.Equal(shmTestData, region.Data()) 123 | if !a.NoError(result.Err) { 124 | t.Log(result.Output) 125 | } 126 | } 127 | 128 | func TestMemoryRegionCreation(t *testing.T) { 129 | a := assert.New(t) 130 | object, err := NewWindowsNativeMemoryObject(defaultObjectName, os.O_CREATE|os.O_RDWR, len(shmTestData)) 131 | if !a.NoError(err) { 132 | return 133 | } 134 | defer func() { 135 | a.NoError(object.Close()) 136 | }() 137 | region, err := mmf.NewMemoryRegion(object, mmf.MEM_READWRITE, 0, len(shmTestData)) 138 | if !a.NoError(err) { 139 | return 140 | } 141 | defer func() { 142 | a.NoError(region.Close()) 143 | }() 144 | region2, err := mmf.NewMemoryRegion(object, mmf.MEM_READWRITE, 0, len(shmTestData)) 145 | if !a.NoError(err) { 146 | return 147 | } 148 | a.NoError(region2.Close()) 149 | } 150 | -------------------------------------------------------------------------------- /internal/common/common_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | // +build darwin freebsd linux 4 | 5 | package common 6 | 7 | import ( 8 | "errors" 9 | "os" 10 | "syscall" 11 | "time" 12 | 13 | "golang.org/x/sys/unix" 14 | ) 15 | 16 | const ( 17 | // IpcCreate flag tells a function to create an object if key is nonexistent. 18 | IpcCreate = 00001000 19 | // IpcExcl flag tells a function to create an object if key is nonexistent and fail if key exists. 20 | IpcExcl = 00002000 21 | // IpcNoWait flag tell a function to return error on wait. 22 | IpcNoWait = 00004000 23 | 24 | // IpcRmid flag tells a function to remove resource. 25 | IpcRmid = 0 26 | // IpcSet flag tells a function to set ipc_perm options. 27 | IpcSet = 1 28 | // IpcStat flag tells a function to get ipc_perm options. 29 | IpcStat = 2 30 | // IpcInfo flag tells a function to retrieve information about an object. 31 | IpcInfo = 3 32 | ) 33 | 34 | // Key is an unsigned integer value considered to be unique for a unique name. 35 | type Key uint64 36 | 37 | // KeyForName generates a key for given path. 38 | func KeyForName(name string) (Key, error) { 39 | name = TmpFilename(name) 40 | file, err := os.Create(name) 41 | if err != nil { 42 | return 0, errors.New("invalid name for key") 43 | } 44 | file.Close() 45 | k, err := ftok(name) 46 | if err != nil { 47 | os.Remove(name) 48 | return 0, errors.New("invalid name for key") 49 | } 50 | return k, nil 51 | } 52 | 53 | // TmpFilename returns a full path for a temporary file with the given name. 54 | func TmpFilename(name string) string { 55 | return os.TempDir() + "/" + name 56 | } 57 | 58 | // AbsTimeoutToTimeSpec converts given timeout value to absulute value of unix.Timespec. 59 | func AbsTimeoutToTimeSpec(timeout time.Duration) *unix.Timespec { 60 | if timeout >= 0 { 61 | ts := unix.NsecToTimespec(time.Now().Add(timeout).UnixNano()) 62 | return &ts 63 | } 64 | return nil 65 | } 66 | 67 | // TimeoutToTimeSpec converts given timeout value to relative value of unix.Timespec. 68 | func TimeoutToTimeSpec(timeout time.Duration) *unix.Timespec { 69 | if timeout >= 0 { 70 | ts := unix.NsecToTimespec(timeout.Nanoseconds()) 71 | return &ts 72 | } 73 | return nil 74 | } 75 | 76 | // IsInterruptedSyscallErr returns true, if the given error is a syscall.EINTR error. 77 | func IsInterruptedSyscallErr(err error) bool { 78 | return SyscallErrHasCode(err, unix.EINTR) 79 | } 80 | 81 | // SyscallNameFromErr returns name of a syscall from a syscall errror. 82 | func SyscallNameFromErr(err error) string { 83 | if sysErr, ok := err.(*os.SyscallError); ok { 84 | return sysErr.Syscall 85 | } 86 | return "" 87 | } 88 | 89 | // UninterruptedSyscall runs a function in a loop. 90 | // If an error, returned by the function is a syscall.EINTR error, 91 | // it runs the function again. Otherwise, it returns the error. 92 | func UninterruptedSyscall(f func() error) error { 93 | for { 94 | err := f() 95 | if !IsInterruptedSyscallErr(err) { 96 | return err 97 | } 98 | } 99 | } 100 | 101 | // UninterruptedSyscallTimeout runs a function in a loop. 102 | // It acts like UninterruptedSyscall, however, before every run it 103 | // recalculates timeout value according to the passed time. 104 | func UninterruptedSyscallTimeout(f func(time.Duration) error, timeout time.Duration) error { 105 | var err error 106 | CallTimeout(func(timeout time.Duration) bool { 107 | if err = f(timeout); IsInterruptedSyscallErr(err) { 108 | return true 109 | } 110 | return false 111 | }, timeout) 112 | return err 113 | } 114 | 115 | func ftok(name string) (Key, error) { 116 | var statfs unix.Stat_t 117 | if err := unix.Stat(name, &statfs); err != nil { 118 | return 0, err 119 | } 120 | // unconvert says there is 'redundant type conversion' to uint64, 121 | // however, this is not always true, as the types of statfs.Ino and statfs.Dev 122 | // may vary on different platforms. 123 | return Key(uint64(statfs.Ino)&0xFFFF | ((uint64(statfs.Dev) & 0xFF) << 16)), nil 124 | } 125 | 126 | // IsTimeoutErr returns true, if the given error is a temporary syscall error. 127 | func IsTimeoutErr(err error) bool { 128 | if sysErr, ok := err.(*os.SyscallError); ok { 129 | if errno, ok := sysErr.Err.(syscall.Errno); ok { 130 | return errno.Timeout() 131 | } 132 | } 133 | return false 134 | } 135 | 136 | // NewTimeoutError returns new syscall error with EAGAIN code. 137 | func NewTimeoutError(op string) error { 138 | return os.NewSyscallError(op, unix.EAGAIN) 139 | } 140 | -------------------------------------------------------------------------------- /shm/shared_memory_native_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Aleksandr Demakin. All rights reserved. 2 | 3 | package shm 4 | 5 | import ( 6 | "os" 7 | 8 | "bitbucket.org/avd/go-ipc/internal/common" 9 | "bitbucket.org/avd/go-ipc/internal/sys/windows" 10 | 11 | "github.com/pkg/errors" 12 | "golang.org/x/sys/windows" 13 | ) 14 | 15 | const ( 16 | // O_COPY_ON_WRITE used as a flag for NewWindowsNativeMemoryObject. 17 | // It is passed to CreateFileMapping and OpebFileMapping calls. 18 | // Its value was chosen simply not to intersect with O_RDONLY and O_RDWR. 19 | O_COPY_ON_WRITE = windows.O_NONBLOCK 20 | ) 21 | 22 | var ( 23 | _ SharedMemoryObject = (*WindowsNativeMemoryObject)(nil) 24 | ) 25 | 26 | // WindowsNativeMemoryObject represents a standart windows shm implementation backed by a paging file. 27 | // Can be used to map shared memory regions into the process' address space. 28 | // It does not follow the usual memory object semantics, and it is destroyed only when all its handles are closed. 29 | // The following functions were added to satisfy SharedMemoryObject interface and return an error: 30 | // Truncate 31 | // Destroy 32 | type WindowsNativeMemoryObject struct { 33 | name string 34 | size int 35 | handle windows.Handle 36 | } 37 | 38 | // NewWindowsNativeMemoryObject returns a new Windows native shared memory object. 39 | // name - object name. 40 | // flag - combination of open flags from 'os' package along with O_COPY_ON_WRITE. 41 | // size - mapping file size. 42 | func NewWindowsNativeMemoryObject(name string, flag, size int) (*WindowsNativeMemoryObject, error) { 43 | prot, sysFlags, err := sysProtAndFlagsFromFlag(flag) 44 | if err != nil { 45 | return nil, errors.Wrap(err, "windows native shm flags check failed") 46 | } 47 | maxSizeHigh := uint32((int64(size)) >> 32) 48 | maxSizeLow := uint32((int64(size)) & 0xFFFFFFFF) 49 | 50 | var handle windows.Handle 51 | creator := func(create bool) error { 52 | if create { 53 | handle, err = sys.CreateFileMapping( 54 | windows.InvalidHandle, 55 | nil, 56 | prot, 57 | maxSizeHigh, 58 | maxSizeLow, 59 | name) 60 | if os.IsExist(err) { 61 | windows.CloseHandle(handle) 62 | } 63 | } else { 64 | handle, err = sys.OpenFileMapping(sysFlags, 0, name) 65 | } 66 | return err 67 | } 68 | 69 | if _, err = common.OpenOrCreate(creator, flag); err != nil { 70 | return nil, errors.Wrap(err, "create mapping file failed") 71 | } 72 | 73 | return &WindowsNativeMemoryObject{name: name, handle: handle, size: size}, nil 74 | } 75 | 76 | // Name returns the name of the object as it was given to NewWindowsNativeMemoryObject(). 77 | func (obj *WindowsNativeMemoryObject) Name() string { 78 | return obj.name 79 | } 80 | 81 | // Fd returns windows.InvalidHandle so that the paging file is used for mapping. 82 | func (obj *WindowsNativeMemoryObject) Fd() uintptr { 83 | return uintptr(obj.handle) 84 | } 85 | 86 | // Size returns mapping fiel size. 87 | func (obj *WindowsNativeMemoryObject) Size() int64 { 88 | return int64(obj.size) 89 | } 90 | 91 | // Close closes mapping file object. 92 | func (obj *WindowsNativeMemoryObject) Close() error { 93 | if err := windows.CloseHandle(obj.handle); err != nil { 94 | return errors.Wrap(err, "close handle failed") 95 | } 96 | return nil 97 | } 98 | 99 | // Truncate returns an error. You can specify the size when calling NewWindowsNativeMemoryObject. 100 | func (obj *WindowsNativeMemoryObject) Truncate(size int64) error { 101 | return errors.New("truncate cannot be done on windows shared memory") 102 | } 103 | 104 | // Destroy returns an error. It is not supported for windows shared memory. 105 | func (obj *WindowsNativeMemoryObject) Destroy() error { 106 | return errors.New("destroy cannot be done on windows shared memory") 107 | } 108 | 109 | // IsNative returns true, indicating, that this object can be mapped without extra call to CreateFileMapping. 110 | func (obj *WindowsNativeMemoryObject) IsNative() bool { 111 | return true 112 | } 113 | 114 | func sysProtAndFlagsFromFlag(flag int) (prot uint32, flags uint32, err error) { 115 | flag = common.FlagsForAccess(flag) 116 | switch flag { 117 | case os.O_RDONLY: 118 | prot = windows.PAGE_READONLY 119 | flags = windows.FILE_MAP_READ 120 | case os.O_RDWR: 121 | prot = windows.PAGE_READWRITE 122 | flags = windows.FILE_MAP_WRITE 123 | case O_COPY_ON_WRITE: 124 | prot = windows.PAGE_WRITECOPY 125 | flags = windows.FILE_MAP_COPY 126 | default: 127 | err = errors.Errorf("invalid mem region flags %d", flag) 128 | } 129 | return 130 | } 131 | -------------------------------------------------------------------------------- /fifo/internal/test/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Aleksandr Demakin. All rights reserved. 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "os" 9 | "strconv" 10 | "time" 11 | 12 | "bitbucket.org/avd/go-ipc/fifo" 13 | "bitbucket.org/avd/go-ipc/internal/test" 14 | ) 15 | 16 | var ( 17 | objName = flag.String("object", "", "shared memory object name") 18 | nonBlock = flag.Bool("nonblock", false, "set O_NONBLOCK flag") 19 | ) 20 | 21 | const usage = ` test program for fifo. 22 | available commands: 23 | create 24 | this operation never blocks 25 | destroy 26 | read len 27 | test {expected values byte array} 28 | write {values byte array} 29 | byte array should be passed as a continuous string of 2-symbol hex byte values like '01020A' 30 | ` 31 | 32 | func create() error { 33 | if flag.NArg() != 1 { 34 | return fmt.Errorf("create: must provide exactly one arguments") 35 | } 36 | mode := os.O_CREATE | fifo.O_NONBLOCK | os.O_RDONLY 37 | obj, err := fifo.New(*objName, mode, 0666) 38 | if err != nil { 39 | return err 40 | } 41 | obj.Close() 42 | return nil 43 | } 44 | 45 | func destroy() error { 46 | if flag.NArg() != 1 { 47 | return fmt.Errorf("destroy: must not provide any arguments") 48 | } 49 | return fifo.Destroy(*objName) 50 | } 51 | 52 | func read() error { 53 | if flag.NArg() != 2 { 54 | return fmt.Errorf("read: must provide exactly one arguments") 55 | } 56 | length, err := strconv.Atoi(flag.Arg(1)) 57 | if err != nil { 58 | return err 59 | } 60 | mode := os.O_CREATE | os.O_RDONLY 61 | if *nonBlock { 62 | mode |= fifo.O_NONBLOCK 63 | } 64 | obj, err := fifo.New(*objName, mode, 0666) 65 | if err != nil { 66 | return err 67 | } 68 | defer obj.Close() 69 | buffer := make([]byte, length) 70 | var n int 71 | if n, err = obj.Read(buffer); err == nil { 72 | if n == length { 73 | if length > 0 { 74 | fmt.Println(testutil.BytesToString(buffer)) 75 | } 76 | } else { 77 | err = fmt.Errorf("wanted %d bytes, but got %d", length, n) 78 | } 79 | } 80 | return err 81 | } 82 | 83 | func test() error { 84 | if flag.NArg() != 2 { 85 | return fmt.Errorf("test: must provide exactly one arguments") 86 | } 87 | mode := os.O_CREATE | os.O_RDONLY 88 | if *nonBlock { 89 | mode |= fifo.O_NONBLOCK 90 | } 91 | var obj fifo.Fifo 92 | var err error 93 | completed := testutil.WaitForFunc(func() { 94 | obj, err = fifo.New(*objName, mode, 0666) 95 | }, time.Second*2) 96 | if !completed { 97 | return fmt.Errorf("fifo.New took too long to finish") 98 | } 99 | if err != nil { 100 | return err 101 | } 102 | defer obj.Close() 103 | data, err := testutil.StringToBytes(flag.Arg(1)) 104 | if err != nil { 105 | return err 106 | } 107 | completed = testutil.WaitForFunc(func() { 108 | buffer := make([]byte, len(data)) 109 | if _, err = obj.Read(buffer); err == nil { 110 | for i, value := range data { 111 | if value != buffer[i] { 112 | err = fmt.Errorf("invalid value at %d. expected '%d', got '%d'", i, value, buffer[i]) 113 | return 114 | } 115 | } 116 | } 117 | }, time.Second*2) 118 | if !completed { 119 | return fmt.Errorf("read took too long to finish") 120 | } 121 | return err 122 | } 123 | 124 | func write() error { 125 | if flag.NArg() != 2 { 126 | return fmt.Errorf("test: must provide exactly one arguments") 127 | } 128 | mode := os.O_CREATE | os.O_WRONLY 129 | if *nonBlock { 130 | mode |= fifo.O_NONBLOCK 131 | } 132 | obj, err := fifo.New(*objName, mode, 0666) 133 | if err != nil { 134 | return err 135 | } 136 | defer obj.Close() 137 | data, err := testutil.StringToBytes(flag.Arg(1)) 138 | if err != nil { 139 | return err 140 | } 141 | var written int 142 | if written, err = obj.Write(data); err == nil && written != len(data) { 143 | err = fmt.Errorf("must write %d bytes, but wrote %d", len(data), written) 144 | } 145 | return err 146 | } 147 | 148 | func runCommand() error { 149 | command := flag.Arg(0) 150 | switch command { 151 | case "create": 152 | return create() 153 | case "destroy": 154 | return destroy() 155 | case "read": 156 | return read() 157 | case "test": 158 | return test() 159 | case "write": 160 | return write() 161 | default: 162 | return fmt.Errorf("unknown command") 163 | } 164 | } 165 | 166 | func main() { 167 | flag.Parse() 168 | if len(*objName) == 0 || flag.NArg() == 0 { 169 | fmt.Print(usage) 170 | flag.Usage() 171 | os.Exit(1) 172 | } 173 | if err := runCommand(); err != nil { 174 | fmt.Fprintf(os.Stderr, "%v\n", err) 175 | os.Exit(1) 176 | } 177 | } 178 | --------------------------------------------------------------------------------