WORKDIR /playpen
# Install RMr (runtime and dev) from debian package cached on packagecloud.io
-ARG RMR_VER=4.5.2
+ARG RMR_VER=4.6.0
ARG RMR_PC_REPO=release
#ARG RMR_PC_REPO=staging
---
-tag: '1.9.0'
+tag: '1.10.0'
# this is used by CI jobs to apply a tag when it builds the image
char* done = NULL;
if( (ep = getenv( "MCL_RDC_ENABLE" )) != NULL && atoi( ep ) == 0 ) { // exists and is 0
- logit( LOG_INFO, "(mcl) raw data capture disabled by environment var setting (MCL_RDCENABLE=0)\n" );
+ logit( LOG_INFO, "(mcl) raw data capture disabled by environment var setting (MCL_RDCENABLE=0)" );
return NULL;
}
<delim><len><timestamp>
Field lengths (bytes) are:
- delim 4
+ delim 4
len 8 (7 digits + 0)
timestamp 16 (15 digits + 0)
fifo = NULL;
}
}
+ } else {
+ if( fifo->fd < 0 ) { // it existed, but was closed; reopen
+ fifo->fd = open_fifo( ctx, mtype, io_dir );
+ }
}
if( fref != NULL ) {
return fifo == NULL ? -1 : fifo->fd;
}
+/*
+ Should we need to close a FIFO we do so and leave the block in the hash
+ with a bad FD so that we'll attempt to reopen on next use.
+*/
+static void close_fifo( mcl_ctx_t* ctx, int mtype, int io_dir ) {
+ fifo_t* fifo;
+ void* hash;
+
+ if( ctx == NULL ) {
+ return;
+ }
+
+ if( io_dir == READER ) { // with an integer key, we need two hash tables
+ hash = ctx->rd_hash;
+ } else {
+ hash = ctx->wr_hash;
+ }
+
+ if( (fifo = (fifo_t *) rmr_sym_pull( hash, mtype )) != NULL ) {
+ if( fifo->fd >= 0 ) {
+ close( fifo->fd );
+ fifo->fd = -1;
+ }
+ }
+}
+
/*
Make marking counts easier in code
*/
}
}
+/*
+ Writes the indicated bytes (n2write) from buf onto the fd. Returns only after
+ full buffer is written, or there is a hard error (not eagain or eintr).
+ Returns the number written; if less than n2write the caller may assume
+ that there was a hard error and errno should reflect.
+*/
+static inline int write_all( int fd, char const* buf, int n2write ) {
+ ssize_t remain = 0; // number of bytes remaining to write
+ ssize_t wrote = 0; // number of bytes written thus far
+ ssize_t state = 0;
+
+ if( fd < 0 ) {
+ errno = EBADFD;
+ return 0;
+ }
+
+ errno = 0;
+ remain = n2write;
+ do {
+ if( (state = write( fd, buf + wrote, remain )) > 0 ) {
+ wrote += state;
+ remain = n2write - wrote;
+ }
+ } while( remain > 0 && (errno == EINTR || errno == EAGAIN) ) ;
+
+ return wrote;
+}
+
+/*
+ Similar to write_all, this will write all bytes in the buffer, but
+ will return failure if the first write attempt fails with 0 written
+ (assuming that the pipe has no reader). We use this when writing the
+ header bytes; we want to drop the message if we can't even write one
+ byte, but if we write one, we must loop until all are written.
+
+ Returns the number written. If that value is less than n2write, then
+ the caller may assume a hard error occurred and errno should reflect.
+ If 0 is returned it can be assumed that the FIFO would block/has no
+ reader.
+*/
+static inline int write_all_nb( int fd, char const* buf, int n2write ) {
+ ssize_t remain = 0; // number of bytes remaining to write
+ ssize_t wrote = 0; // number of bytes written
+
+ if( fd < 0 ) {
+ errno = EBADFD;
+ return 0;
+ }
+
+ errno = 0;
+ remain = n2write;
+ wrote = write( fd, buf, remain );
+ if( wrote < 0 ) { // report error with exception for broken pipe
+ return errno == EPIPE ? 0 : -1; // broken pipe we assume no reader and return 0 since nothing written
+ }
+
+ if( wrote < n2write && wrote > 0 ) { // if we wrote anything, we must tough it out and write all if it was short
+ wrote += write_all( fd, buf + wrote, n2write - wrote );
+ }
+
+ return wrote;
+}
+
// ---------- public ------------------------------------------------------
/*
Sets a signal handler for sigpipe so we don't crash if a reader closes the
time_t next_report = 0; // we'll report every 2 seconds if report is true
time_t now;
size_t hwlen; // write len for header
+ size_t wrote; // number of bytes actually written
void* rdc_ctx = NULL; // raw data capture context
void* rdc_buf = NULL; // capture buffer
hwlen = MCL_LEN_SIZE;
}
- fd = suss_fifo( ctx, mbuf->mtype, WRITER, &fifo ); // map the message type to an open fd
+ fd = suss_fifo( ctx, mbuf->mtype, WRITER, &fifo ); // map the message type to an open fd
if( fd >= 0 ) {
- if( write( fd, header, hwlen ) != hwlen ) { // write exactly MCL_LEN_SIZE bytes from the buffer
+ if( (wrote = write_all_nb( fd, header, hwlen )) == 0 ) { // write header; 0 indicates no reader, drop silently
drops++;
total_drops++;
chalk_error( fifo );
} else {
- if( write( fd, mbuf->payload, mbuf->len ) < mbuf->len ) { // followed by the payload
+ if( wrote != hwlen ) {
+ logit( LOG_ERR, "(mcl): error writing header to fifo; mt=%d wrote=%d tried=%d: %s", mbuf->mtype, wrote, hwlen, strerror( errno ) );
errors++;
chalk_error( fifo );
+ close_fifo( ctx, mbuf->mtype, WRITER );
} else {
- chalk_ok( fifo );
- count++;
- total++;
+ if( write_all( fd, mbuf->payload, mbuf->len ) != mbuf->len ) { // we wrote a header, so we must write all; no drop at this point
+ logit( LOG_ERR, "(mcl): error writing payload to fifo; mt=%d: %s\n", mbuf->mtype, strerror( errno ) );
+ close_fifo( ctx, mbuf->mtype, WRITER );
+ } else {
+ chalk_ok( fifo );
+ count++;
+ total++;
+ }
}
}
}
- if( rdc_ctx != NULL ) {
+ if( rdc_ctx != NULL ) { // always put the message to the rdc files if collecting; eve if pipe write failed
rdc_buf = rdc_init_buf( mbuf->mtype, header, hwlen, rdc_buf ); // set up for write
rdc_write( rdc_ctx, rdc_buf, mbuf->payload, mbuf->len ); // write the raw data
}
fprintf( stderr, " -d dir (default is /tmp/mcl/fifos)\n" );
fprintf( stderr, " -e disable extended headers expectation in FIFO data\n" );
fprintf( stderr, " -m msg-type (default is 0)\n" );
+ fprintf( stderr, " -M max msg to read (default is all)\n" );
fprintf( stderr, " -s stats only mode\n" );
}
char timestamp[MCL_TSTAMP_SIZE]; // we'll get the timestamp from this
long count = 0;
int blabber = 0;
+ int max = 0; // we'll force one reader down early to simulate MC going away
signal( SIGINT, sigh );
signal( SIGTERM, sigh );
}
break;
+ case 'M':
+ if( pidx+1 < argc ) {
+ max = atoi( argv[pidx+1] );
+ pidx++;
+ } else {
+ bad_arg( argv[pidx] );
+ error = 1;
+ }
+ break;
+
case 's':
stats_only = 1;
break;
exit( 1 );
}
- while( 1 ) {
+ fprintf( stderr, "[INFO] max = %d\n", max );
+ while( max == 0 || count < max ) {
len = mcl_fifo_tsread1( ctx, mtype, buf, sizeof( buf ) -1, long_hdrs, timestamp );
if( len > 0 ) {
if( stats_only ) {
}
count++;
+ } else {
+ sleep( 1 );
}
}
+
+ fprintf( stderr, "[INFO] max reached: %d\n", max );
}
sbuf = rmr_send_msg( mrc, sbuf ); // retry send until it's good (simple test; real programmes should do better)
}
count++;
- fprintf( stderr, "<SNDR> sent message\n" );
+ fprintf( stderr, "<SNDR> sent message type=%d\n", mtype );
usleep( delay );
mtype++;
- if( mtype > 6 ) {
+ if( mtype > 8 ) { // ensure we send a mt that doesn't have a fifo reader to ensure we don't block
mtype = 1;
}
}
# run a pipe reader for one message type
function run_pr {
- echo "starting pipe reader $1"
- $bin_dir/pipe_reader $ext_hdr -m $1 -d $fifo_dir >/tmp/pr.$1.log 2>&1 &
- #$bin_dir/pipe_reader -m $1 -d $fifo_dir & # >/tmp/pr.$1.log 2>&1
+ echo "starting pipe reader $1 $max_flag"
+ set -x
+ $bin_dir/pipe_reader $ext_hdr $max_flag -m $1 -d $fifo_dir >/tmp/pr.$1.log 2>&1 &
+ set +x
typeset prpid=$!
-
+
sleep $reader_wait
echo "stopping pipe reader $ppid"
kill -15 $prpid
function gen_rt {
cat <<endKat >/tmp/local.rt
newrt|start
- mse | 0 | -1 | localhost:4560
- mse | 1 | -1 | localhost:4560
- mse | 2 | -1 | localhost:4560
- mse | 3 | -1 | localhost:4560
- mse | 4 | -1 | localhost:4560
- mse | 5 | -1 | localhost:4560
- mse | 6 | -1 | localhost:4560
+ mse | 0 | -1 | localhost:4560
+ mse | 1 | -1 | localhost:4560
+ mse | 2 | -1 | localhost:4560
+ mse | 3 | -1 | localhost:4560
+ mse | 4 | -1 | localhost:4560
+ mse | 5 | -1 | localhost:4560
+ mse | 6 | -1 | localhost:4560
+ mse | 7 | -1 | localhost:4560
+ mse | 8 | -1 | localhost:4560
+ mse | 9 | -1 | localhost:4560
newrt|end
endKat
}
ext_hdr="" # run with extended header enabled (-e turns extended off)
long_test=0
raw_capture=1
+verbose=0
while [[ $1 == -* ]]
do
- case $1 in
+ case $1 in
-l) long_test=1;;
-n) raw_capture=0;;
-s) si="si_";;
+ -v) verbose=1;;
*) echo "$1 is not a recognised option"
exit 1
;;
run_listener &
sleep 4
-for p in 0 1 2 3 4 5 6
+# the sender will send types 0-8 inclusive; we only start 7 readers to
+# endure listener doesn't hang on pipes without a reader
+#
+for p in 0 1 2 3 4 5
do
- run_pr $p &
+ run_pr $p & # all but last have no max read
done
-sleep 1
+max_flag="-M 10"
+run_pr 6 &
+
+sleep 1 # let the readers settle
run_sender &
sleep $main_wait # long enough for all functions to finish w/o having to risk a wait hanging
echo "all functions stopped; looking at logs"
+if (( verbose ))
+then
+ echo "[INFO] ---- mc_lisener log follwos --------------------------"
+ cat /tmp/listen.log
+ echo "[INFO] ------------------------------------------------------"
+fi
+
+
# ---------- validation -------------------------------------------------
errors=0
fi
done
-if (( ! errors ))
+if (( ! errors ))
then
echo "[OK] All logs seem good"
fi
then
echo "[OK] Found final direcory ($final_dir)"
ls -al $final_dir
-
+
if (( long_test )) # look for files in final dir to ensure roll
then
found=$( ls $final_dir/MC* | wc -l )
chmod 644 $file
set -x
- $bin_dir/rdc_replay -f $file -d $fifo_dir >/tmp/replay.log 2>&1
+ $bin_dir/rdc_replay -f $file -d $fifo_dir >$log_dir/replay.log 2>&1
lpid=$!
set +x
echo "replay finished"
# run a pipe reader for one message type
function run_pr {
echo "starting pipe reader $1"
- $bin_dir/pipe_reader $ext_hdr -m $1 -d $fifo_dir >/tmp/pr.$1.log 2>&1 &
- #$bin_dir/pipe_reader -m $1 -d $fifo_dir & # >/tmp/pr.$1.log 2>&1
+ $bin_dir/pipe_reader $ext_hdr -m $1 -d $fifo_dir >$log_dir/pr.$1.log 2>&1 &
typeset prpid=$!
sleep $reader_wait
# ---- run everything ---------------------------------------------------
+log_dir=/tmp/mcl_verify
+mkdir -p $log_dir
+
ext_hdr="" # run with extended header enabled (-e turns extended off)
run_listener=0 # -a turns on to run all
while [[ $1 == -* ]]
# logs should be > 0 in size
echo "----- logs ---------"
-ls -al /tmp/*.log
+ls -al $log_dir/*.log
# pipe reader log files 1-6 should have 'stand up and cheer' messages
# pipe reader log for MT 0 will likley be empty as sender sends only
#
for l in 1 2 3 4 5 6
do
- if [[ ! -s /tmp/pr.$l.log ]]
+ if [[ ! -s $log_dir/pr.$l.log ]]
then
echo "[FAIL] log $l was empty"
(( errors++ ))
else
- if ! grep -q -i "stand up and cheer" /tmp/pr.$l.log
+ if ! grep -q -i "stand up and cheer" $log_dir/pr.$l.log
then
- echo "[FAIL] pipe reader log did not have any valid messages: /tmp/pr.$l.log"
+ echo "[FAIL] pipe reader log did not have any valid messages: $log_dir/pr.$l.log"
(( errors++ ))
fi
fi
as the test sender, to generate coverage. There is little vetting done
here; any problems should be discovered by the verify scripts in ../src
at the time the code is pushed. This is simply a coverage test for sonar.
+
+
+Verification
+The unit and app test scripts pretty much verify sucessful runs. If it is
+necessary to see things like whether or not the "failure" of a pipe
+reader affects the listener (blocks it), then running the app test with
+-v and looking at the listener output will help. Lines like the following
+give clues about this:
+
+1616516620 [STAT] (mcl) mtype=0 total writes=1 total drops=0; during last 1s writes=0 drops=0
+1616516620 [STAT] (mcl) mtype=1 total writes=88 total drops=0; during last 1s writes=24 drops=0
+1616516620 [STAT] (mcl) mtype=2 total writes=88 total drops=0; during last 1s writes=24 drops=0
+1616516620 [STAT] (mcl) mtype=3 total writes=88 total drops=0; during last 1s writes=24 drops=0
+1616516620 [STAT] (mcl) mtype=4 total writes=88 total drops=0; during last 1s writes=24 drops=0
+1616516620 [STAT] (mcl) mtype=5 total writes=88 total drops=0; during last 1s writes=24 drops=0
+1616516620 [STAT] (mcl) mtype=6 total writes=10 total drops=78; during last 1s writes=0 drops=25
+1616516620 [STAT] (mcl) mtype=7 total writes=0 total drops=88; during last 1s writes=0 drops=25
+1616516620 [STAT] (mcl) mtype=8 total writes=0 total drops=88; during last 1s writes=0 drops=25
+
+The sender sends message types 0-8 inclusive, but there are only 7 listeners
+started (0-6) so we should never see successful wirte counts for types 7 and 8.
+Further, pipe reader for mt 6 stops early, and so we should see some drops after
+it has stopped; It stops after 10 successful reads, so the number of writes
+should be 10 or 11 depending on timing of the processes. There shouldn't be
+any drops on the other FIFOs.
+
+It is difficult, if not impossible, to test the logic that detects an
+interrupted write on the pipe to ensure that all data is written.
# This is a hack! There seems not to be an easy way to have the LF
-# environment add RMR (or other needed packages) for testing. If we don't
+# environment adds RMR (or other needed packages) for testing. If we don't
# find RMR in the /usr/local part of the filesystem, we'll force it into
# /tmp which doesn't require root. We'll be smart and get the desired
-# rmr version from the repo root juas as we _expected_ the CI environmnt
-# woudl do (but seems not to).
+# rmr version from the repo root just as we _expected_ the CI environmnt
+# would do (but seems not to).
#
function ensure_pkgs {
if (( no_rmr_load ))
fi
+verbose=0
while [[ $1 == -* ]]
do
case $1 in
-f) force_rmr_load=1;;
-N) no_rmr_load=1;; # for local testing
+ -v) verbose=1; vflag="-v";;
*) echo "unrecognised option: $1"
exit 1
export TEST_COV_OPTS="-ftest-coverage -fprofile-arcs" # picked up by make so we get coverage on tools for sonar
make clean # ensure coverage files removed
make -B # force build under the eyes of sonar build wrapper
+if (( $? != 0 ))
+then
+ echo "[FAIL] build failed"
+ exit
+fi
rm -fr *.gcov *.gcda # ditch any previously generated coverage info
./rdc_replay -$x >/dev/null 2>&1 # drive each exception (missing value) or 'help'
done
-./verify.sh # verify MUST be first (replay relies on its output)
+./verify.sh $vflag # verify MUST be first (replay relies on its output)
./verify_replay.sh
# generate and copy coverage files to parent which is where the CI jobs are looking for them
running=/tmp/PID$$.running
force_rmr_load=0
ci_mode=1 # -c turns off; see the flower box above
+verbose=0
while [[ $1 == -* ]]
do
-c) ci_mode=0;;
-f) force_rmr_load=1;;
-N) no_rmr_load=1;; # for local testing
+ -v) verbose=1;;
*) echo "unrecognised option: $1"
exit 1
if (( ci_mode )) # in CI mode we must force a build in the src so build wrapper captures trigger data
then
- echo "building in src for sonar build wrapper capture"
+ echo "unit_test: building in src for sonar build wrapper capture"
(
cd ../src
export TEST_COV_OPTS="-ftest-coverage -fprofile-arcs" # picked up by make so we get coverage on tools for sonar
)
fi
-echo "building unit test(s)"
+echo "[INFO] unit_test: building unit test(s)"
if ! make -B unit_test # ensure that it's fresh
then
echo "[FAIL] cannot make unit_test"
exit 1
fi
-echo "unit test(s) successfully built"
+echo "[INFO] unit_test: unit test(s) successfully built"
setup_dirs
+echo "[INFO] unit_test: directories set up"
if [[ $1 == "set"* ]] # setup only
then
+ echo "[INFO] unit_test: setup only; exiting good"
exit
fi
rm -fr *.gcov *.gcda # ditch any previously generated coverage info
+echo "[INFO] unit_test: running tests -- may take as long as 120 seconds"
./unit_test >/tmp/PID$$.utlog 2>&1 &
pid=$!
abort_after 60 $pid &
errors++;
}
+ close_fifo( ctx, TEST_MTYPE, WRITER ); // close one we know is there
+ close_fifo( ctx, 84306, WRITER ); // coverage on error case
+
mcl_start_listening( ctx, port, 0 ); // start the listener
// under test, the FOREVER = 0 keeps fanout from blocking; drive several times to cover all cases