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  }