github.com/lirm/aeron-go@v0.0.0-20230415210743-920325491dc4/systests/driver/media_driver.go (about) 1 /* 2 Copyright 2022 Steven Stern 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package driver 18 19 import ( 20 "errors" 21 "fmt" 22 "github.com/google/uuid" 23 "github.com/lirm/aeron-go/aeron" 24 "github.com/lirm/aeron-go/aeron/logging" 25 "os" 26 "os/exec" 27 "reflect" 28 "syscall" 29 "time" 30 ) 31 32 // Must match Java's `AERON_DIR_PROP_NAME` 33 const aeronDirPropName = "aeron.dir" 34 35 // Must match Java's `DIR_DELETE_ON_START_PROP_NAME` 36 const aeronDirDeleteStartPropName = "aeron.dir.delete.on.start" 37 38 // Must match Java's `DIR_DELETE_ON_SHUTDOWN_PROP_NAME` 39 const aeronDirDeleteShutdownPropName = "aeron.dir.delete.on.shutdown" 40 41 // Must match Java's `CLIENT_LIVENESS_TIMEOUT_PROP_NAME`, measured in nanos 42 const aeronClientLivenessTimeoutNs = "aeron.client.liveness.timeout" 43 44 // Must match Java's `PUBLICATION_UNBLOCK_TIMEOUT_PROP_NAME`, measured in nanos 45 const aeronPublicationUnblockTimeoutNs = "aeron.publication.unblock.timeout" 46 47 const jarName = "driver/aeron-all-1.39.0.jar" 48 49 const mediaDriverClassName = "io.aeron.driver.MediaDriver" 50 51 var logger = logging.MustGetLogger("systests") 52 53 type MediaDriver struct { 54 TempDir string 55 cmd *exec.Cmd 56 cxn *aeron.Aeron 57 } 58 59 func StartMediaDriver() (*MediaDriver, error) { 60 tempDir := aeronUniqueTempDir() 61 cmd := setupCmd(tempDir) 62 setupPdeathsig(cmd) 63 if err := cmd.Start(); err != nil { 64 logger.Error("couldn't start Media Driver: ", err) 65 return nil, err 66 } 67 cxn, err := waitForStartup(tempDir) 68 if err != nil { 69 logger.Error("Media Driver timed out during startup: ", err) 70 killMediaDriver(cmd) 71 return nil, err 72 } 73 return &MediaDriver{tempDir, cmd, cxn}, nil 74 } 75 76 func (mediaDriver MediaDriver) StopMediaDriver() { 77 if err := mediaDriver.cxn.Close(); err != nil { 78 logger.Error(err) 79 } 80 killMediaDriver(mediaDriver.cmd) 81 if err := removeTempDir(mediaDriver.TempDir); err != nil { 82 logger.Errorf("Failed to clean up cxn directories: %s", err) 83 } 84 } 85 86 func removeTempDir(tempDir string) error { 87 return os.RemoveAll(tempDir) 88 } 89 90 func killMediaDriver(cmd *exec.Cmd) { 91 if err := cmd.Process.Kill(); err != nil { 92 logger.Error("Couldn't kill Media Driver:", err) 93 } 94 } 95 96 func aeronUniqueTempDir() string { 97 id := uuid.New().String() 98 return fmt.Sprintf("%s/aeron-%s/%s", 99 aeron.DefaultAeronDir, 100 aeron.UserName, 101 id) 102 } 103 104 func setupCmd(tempDir string) *exec.Cmd { 105 cmd := exec.Command( 106 "java", 107 fmt.Sprintf("-D%s=%s", aeronDirPropName, tempDir), 108 fmt.Sprintf("-D%s=true", aeronDirDeleteStartPropName), 109 fmt.Sprintf("-D%s=true", aeronDirDeleteShutdownPropName), 110 fmt.Sprintf("-D%s=%d", aeronClientLivenessTimeoutNs, time.Minute.Nanoseconds()), 111 fmt.Sprintf("-D%s=%d", aeronPublicationUnblockTimeoutNs, 15*time.Minute.Nanoseconds()), 112 "-XX:+UnlockDiagnosticVMOptions", 113 "-XX:GuaranteedSafepointInterval=300000", 114 "-XX:BiasedLockingStartupDelay=0", 115 "-cp", 116 jarName, 117 mediaDriverClassName, 118 ) 119 cmd.Stdout = os.Stdout 120 cmd.Stderr = os.Stderr 121 return cmd 122 } 123 124 // Setting Pdeathsig kills child processes on shutdown, but this only works on linux. 125 // On other platforms, the media driver could be left stranded if the test panics. 126 // https://stackoverflow.com/questions/34730941/ensure-executables-called-in-go-process-get-killed-when-process-is-killed 127 func setupPdeathsig(cmd *exec.Cmd) { 128 if cmd.SysProcAttr == nil { 129 cmd.SysProcAttr = &syscall.SysProcAttr{} 130 } 131 pdeathsig := reflect.ValueOf(cmd.SysProcAttr).Elem().FieldByName("Pdeathsig") 132 if pdeathsig.IsValid() { 133 pdeathsig.Set(reflect.ValueOf(syscall.SIGTERM)) 134 } 135 } 136 137 func waitForStartup(tempDir string) (*aeron.Aeron, error) { 138 timeout := time.Now().Add(5 * time.Second) 139 sleepDuration := 50 * time.Millisecond 140 ctx := aeron.NewContext().AeronDir(tempDir).MediaDriverTimeout(10 * time.Second) 141 channel := "aeron:ipc" 142 streamID := int32(1) 143 for time.Now().Before(timeout) { 144 cxn, err := aeron.Connect(ctx) 145 if err != nil { 146 time.Sleep(sleepDuration) 147 continue 148 } 149 pub, err := cxn.AddPublication(channel, streamID) 150 if err != nil { 151 return nil, err 152 } 153 if err := pub.Close(); err != nil { 154 return nil, err 155 } 156 return cxn, nil 157 } 158 return nil, errors.New("timed out waiting for Media Driver connection") 159 }