Skip to content

Commit 1cc2cfa

Browse files
committed
fifosync: cross-process synchronization
Signed-off-by: Samuel Karp <[email protected]> (cherry picked from commit 5f37a2c) Signed-off-by: Samuel Karp <[email protected]>
1 parent b19be30 commit 1cc2cfa

File tree

1 file changed

+125
-0
lines changed

1 file changed

+125
-0
lines changed

pkg/fifosync/fifo_unix.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//go:build unix
2+
3+
/*
4+
Copyright The containerd Authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
/*
20+
Package fifosync provides a pattern on Unix-like operating systems for synchronizing across processes using Unix FIFOs
21+
(named pipes).
22+
*/
23+
package fifosync
24+
25+
import (
26+
"errors"
27+
"fmt"
28+
"io"
29+
"os"
30+
31+
"golang.org/x/sys/unix"
32+
)
33+
34+
// Trigger is a FIFO which is used to signal another process to proceed.
35+
type Trigger interface {
36+
// Name returns the name of the trigger
37+
Name() string
38+
// Trigger triggers another process to proceed.
39+
Trigger() error
40+
}
41+
42+
// Waiter is a FIFO which is used to wait for trigger provided by another process.
43+
type Waiter interface {
44+
// Name returns the name of the waiter
45+
Name() string
46+
// Wait waits for a trigger from another process.
47+
Wait() error
48+
}
49+
50+
type fifo struct {
51+
name string
52+
}
53+
54+
// NewTrigger creates a new Trigger
55+
func NewTrigger(name string, mode uint32) (Trigger, error) {
56+
return new(name, mode)
57+
}
58+
59+
// NewWaiter creates a new Waiter
60+
func NewWaiter(name string, mode uint32) (Waiter, error) {
61+
return new(name, mode)
62+
}
63+
64+
// New creates a new FIFO if it does not already exist. Use AsTrigger or AsWaiter to convert the new FIFO to a Trigger
65+
// or Waiter.
66+
func new(name string, mode uint32) (*fifo, error) {
67+
s, err := os.Stat(name)
68+
exist := true
69+
if err != nil {
70+
if !errors.Is(err, os.ErrNotExist) {
71+
return nil, fmt.Errorf("fifo: failed to stat %q: %w", name, err)
72+
}
73+
exist = false
74+
}
75+
if s != nil && s.Mode()&os.ModeNamedPipe == 0 {
76+
return nil, fmt.Errorf("fifo: not a named pipe: %q", name)
77+
}
78+
if !exist {
79+
err = unix.Mkfifo(name, mode)
80+
if err != nil && !errors.Is(err, unix.EEXIST) {
81+
return nil, fmt.Errorf("fifo: failed to create %q: %w", name, err)
82+
}
83+
}
84+
return &fifo{
85+
name: name,
86+
}, nil
87+
}
88+
89+
func (f *fifo) Name() string {
90+
return f.name
91+
}
92+
93+
// AsTrigger converts the FIFO to a Trigger.
94+
func (f *fifo) AsTrigger() Trigger {
95+
return f
96+
}
97+
98+
// Trigger triggers another process to proceed.
99+
func (f *fifo) Trigger() error {
100+
file, err := os.OpenFile(f.name, os.O_RDONLY, 0)
101+
if err != nil {
102+
return fmt.Errorf("fifo: failed to open %s: %w", f.name, err)
103+
}
104+
defer file.Close()
105+
_, err = io.ReadAll(file)
106+
return err
107+
}
108+
109+
// AsWaiter converts the FIFO to a Waiter.
110+
func (f *fifo) AsWaiter() Waiter {
111+
return f
112+
}
113+
114+
// Wait waits for a trigger from another process.
115+
func (f *fifo) Wait() error {
116+
fd, err := unix.Open(f.name, unix.O_WRONLY, 0)
117+
if err != nil {
118+
return fmt.Errorf("fifo: failed to open %s: %w", f.name, err)
119+
}
120+
defer unix.Close(fd)
121+
if _, err := unix.Write(fd, []byte("0")); err != nil {
122+
return fmt.Errorf("failed to write to %d: %w", fd, err)
123+
}
124+
return nil
125+
}

0 commit comments

Comments
 (0)