This change adds a new field to the message buffer: tp_status.
The field allows the underlying "errno" value set by the
transport provider to be returned, in addition to the RMR
status, as a part of the overall message. In some environments
wrapper code (bindings) does not hav access to the errno value
and thus was unable to have the additional information on
send and receive failures.
The new field constitutes a backwards compatable change to RMR's
API, however the python wrapper API does not change. Minor version
bump, but the wrapper will see only a patch level bump.
Signed-off-by: E. Scott Daniels <daniels@research.att.com>
Change-Id: I1b8a863318e2b2515441f7339b41c2856e46c1e4
API and build change and fixe summaries. Doc correctsions
and/or changes are not mentioned here; see the commit messages.
+2019 August 8; version 1.1.0 (API change)
+ This change should be backward compatable/non-breaking
+ A new field has been added to the message buffer (rmr_mbuf_t).
+ This field (tp_state) is used to communicate the errno value
+ that the transport mechanism might set during send and/or
+ receive operations. C programmes should continue to use
+ errno directly, but in some environments wrappers may not be
+ able to access errno and this provides the value to them.
+ See the rmr_alloc_msg manual page for more details.
+
2019 August 6; version 1.0.45 (build changes)
Support for the Nanomsg transport library has been dropped.
The library librmr.* will no longer be included in packages.
cmake_minimum_required( VERSION 3.5 )
set( major_version "1" ) # should be automatically populated from git tag later, but until CI process sets a tag we use this
-set( minor_version "0" )
-set( patch_level "45" )
+set( minor_version "1" )
+set( patch_level "0" )
set( install_root "${CMAKE_INSTALL_PREFIX}" )
set( install_inc "include/rmr" )
.if false
==================================================================================
- Copyright (c) 2019 Nokia
+ Copyright (c) 2019 Nokia
Copyright (c) 2018-2019 AT&T Intellectual Property.
Licensed under the Apache License, Version 2.0 (the "License");
&uindent
&h2(DESCRIPTION)
-The &cw(rmr_alloc_msg) function is used to allocate a buffer which the user
+The &cw(rmr_alloc_msg) function is used to allocate a buffer which the user
programme can write into and then send through the RMR library.
The buffer is allocated such that sending it requires no additional copying
-out of the buffer.
+out of the buffer.
If the value passed in &cw(size) is 0, then the default size supplied on the
-&ital(rmr_init) call will be used.
+&ital(rmr_init) call will be used.
The &ital(ctx) parameter is the void context pointer that was returned by
the &ital(rmr_init) function.
&space
-The pointer to the message buffer returned is a structure which has some
+The pointer to the message buffer returned is a structure which has some
user application visible fields; the structure is described in &cw(rmr.h,)
and is illustrated below.
int len;
unsigned char* payload;
unsigned char* xaction;
+ int sub_id;
+ int tp_state;
} rmr_mbuf_t;
&ex_end
&space
&beg_dlist(.75i : ^&bold_font )
-&ditem(state ) Is the current buffer state. Following a call to &cw(rmr_send_msg)
+&ditem(state) Is the current buffer state. Following a call to &cw(rmr_send_msg)
the state indicates whether the buffer was successfully sent which determines
exactly what the payload points to. If the send failed, the payload referenced
-by the buffer is the message that failed to send (allowing the application to
-attempt a retransmission).
+by the buffer is the message that failed to send (allowing the application to
+attempt a retransmission).
When the state is &cw(RMR_OK) the buffer represents an empty buffer that the application
may fill in in preparation to send.
&half_space
-&ditem(mtype ) When sending a message, the application is expected to set this field
+&ditem(mtype) When sending a message, the application is expected to set this field
to the appropriate message type value (as determined by the user programme). Upon send
this value determines how the RMR library will route the message.
For a buffer which has been received, this field will contain the message type that was
-set by the sending application.
+set by the sending application.
&half_space
-&ditem(len ) The application using a buffer to send a message is expected to set the
+&ditem(len) The application using a buffer to send a message is expected to set the
length value to the actual number of bytes that it placed into the message. This
is likely less than the total number of bytes that the message can carry.
For a message buffer that is passed to the application as the result of a receive
-call, this will be the value that the sending application supplied and should
+call, this will be the value that the sending application supplied and should
indicate the number of bytes in the payload which are valid.
&half_space
-&ditem(payload ) The payload is a pointer to the actual received data. The
+&ditem(payload) The payload is a pointer to the actual received data. The
user programme may read and write from/to the memory referenced by the payload
up until the point in time that the buffer is used on a &cw(rmr_send, rmr_call)
-or &cw(rmr_reply) function call.
+or &cw(rmr_reply) function call.
Once the buffer has been passed back to a RMR library function the user programme
should &bold(NOT) make use of the payload pointer.
&half_space
-&ditem(xaction) The &ital(xaction) field is a pointer to a fixed sized area in
-the message into which the user may write a transaction ID.
+&ditem(xaction) The &ital(xaction) field is a pointer to a fixed sized area in
+the message into which the user may write a transaction ID.
The ID is optional with the exception of when the user application uses the &cw(rmr_call)
function to send a message and wait for the reply; the underlying RMR processing
expects that the matching reply message will also contain the same data in the
&ital(xaction) field.
&end_dlist
+&half_space
+&ditem(sub_id)
+This value is the subscription ID. It, in combination with the message type is used
+by rmr to determine the target endpoint when sending a message.
+If the application to application protocol does not warrant the use of a subscription
+ID, the RMR constant RMR_VOID_SUBID should be placed in this field.
+When an application is forwarding or returning a buffer to the sender, it is the
+application's responsibility to set/reset this value.
+
+&half_space
+&ditem(tp_state)
+For C applications making use of RMR, the state of a transport based failure will
+often be available via &cw(errno.)
+However, some wrapper environments may not have direct access to the C-lib &cw(errno)
+value.
+RMR send and receive operations will place the current value of &cw(errno) into this
+field which should make it available to wrapper functions.
+User applications are strongly cautioned against relying on the value of errno as
+some transport mechanisms may not set this value on all calls.
+This value should also be ignored any time the message status is &cw(RMR_OK.)
+
&h2(RETURN VALUE)
The function returns a pointer to a &cw(rmr_mbuf) structure, or NULL on error.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
+## [0.10.4] - 8/08/2019
+ * Fix underlying problem getting errno from some environments; now references new RMR message field to get errno value.
+ * Add /usr/local/lib64 to tox environment variable to support systems where libraries natually install in lib64 rather than lib.
+
## [0.10.3] - 7/31/2019
* (Correctly) Include license here per Jira RICPLT-1855
import json
from ctypes import RTLD_GLOBAL, Structure, c_int, POINTER, c_char, c_char_p, c_void_p, memmove, cast
from ctypes import CDLL
-from ctypes import create_string_buffer, pythonapi
+from ctypes import create_string_buffer
# https://docs.python.org/3.7/library/ctypes.html
# https://stackoverflow.com/questions/2327344/ctypes-loading-a-c-shared-library-that-has-dependencies/30845750#30845750
return sdict.get(stateno, "UNKNOWN STATE")
-def _errno():
- """Suss out the C error number value which might be useful in understanding
- an underlying reason when RMr returns a failure.
- """
- return c_int.in_dll(pythonapi, "errno").value
-
-
##############
# PUBLIC API
##############
unsigned char* payload; // transported data
unsigned char* xaction; // pointer to fixed length transaction id bytes
int sub_id; // subscription id
+ int tp_state; // transport state (a.k.a errno)
// these things are off limits to the user application
void* tp_buf; // underlying transport allocated pointer (e.g. nng message)
void* header; // internal message header (whole buffer: header+payload)
), # according to th following the python bytes are already unsinged https://bytes.com/topic/python/answers/695078-ctypes-unsigned-char
("xaction", POINTER(c_char)),
("sub_id", c_int),
+ ("tp_state", c_int),
]
"payload max size": rmr_payload_size(ptr_to_rmr_buf_t),
"meid": meid,
"message source": get_src(ptr_to_rmr_buf_t),
- "errno": _errno(),
+ "errno": ptr_to_rmr_buf_t.contents.tp_state,
}
sbuf.contents.payload = payload
sbuf.contents.len = len(payload)
sbuf.contents.state = msg_state
+ if msg_state != 0: # set something in transport state if 'error'
+ sbuf.contents.tp_state = 99
+ else:
+ sbuf.contents.tp_state = 0
return sbuf
return f
def f(_unused, sbuf):
sbuf.contents.state = msg_state
+ if msg_state != 0: # set something in transport state if 'error'
+ sbuf.contents.tp_state = 99
+ else:
+ sbuf.contents.tp_state = 0
return sbuf
return f
self.payload = ""
self.xaction = uuid.uuid1().hex.encode("utf-8")
self.sub_id = 0
+ self.tp_state = 0
def __str__(self):
return str(
"payload": self.payload,
"xaction": self.xaction,
"sub_id": self.sub_id,
+ "tp_state": self.tp_state,
}
)
setup(
name="rmr",
- version="0.10.3",
+ version="0.10.4",
packages=find_packages(),
- author="Tommy Carpenter",
+ author="Tommy Carpenter, E. Scott Daniels",
description="Python wrapper for RIC RMR",
url="https://gerrit.o-ran-sc.org/r/admin/repos/ric-plt/lib/rmr",
license="Apache 2.0",
pytest
coverage
pytest-cov
-setenv = LD_LIBRARY_PATH = /usr/local/lib/
+setenv = LD_LIBRARY_PATH = /usr/local/lib/:/usr/local/lib64
commands=pytest --verbose --cov {envsitepackagesdir}/rmr --cov-report html
[testenv:flake8]
(All fields are exposed such that if a wrapper needs to dup the storage as it passes
into or out of their environment they dup it all, not just what we choose to expose.)
+
+ NOTE:
+ State is the RMR state of processing on the message. The transport state (tp_state)
+ will be set to mirror the value of errno for wrappers unable to access errno directly,
+ but will only be set if state is not RMR_OK. Even then, the value may be suspect as
+ the underlying transport mechanism may not set errno. It is strongly recommended that
+ user applications use tp_state only for dianostic purposes to convey additional information
+ in a log message.
*/
typedef struct {
int state; // state of processing
unsigned char* payload; // transported data
unsigned char* xaction; // pointer to fixed length transaction id bytes
int sub_id; // subscription id
+ int tp_state; // transport state (errno) valid only if state != RMR_OK, and even then may not be valid
// these things are off limits to the user application
void* tp_buf; // underlying transport allocated pointer (e.g. nng message)
errno = EINVAL; // if msg is null, this is their clue
if( msg != NULL ) {
msg->state = RMR_ERR_BADARG;
+ msg->tp_state = errno;
}
return msg;
}
if( msg->header == NULL ) {
fprintf( stderr, "[ERR] rmr_send_msg: message had no header\n" );
msg->state = RMR_ERR_NOHDR;
+ msg->tp_state = errno;
return msg;
}
if( msg->state != RMR_ERR_RETRY ) {
msg->state = RMR_ERR_CALLFAILED; // errno not available to all wrappers; don't stomp if marked retry
}
+ msg->tp_state = errno;
return msg;
}
rmr_mbuf_t* qm; // message that was queued on the ring
if( (ctx = (uta_ctx_t *) vctx) == NULL ) {
+ errno = EINVAL;
if( old_msg != NULL ) {
old_msg->state = RMR_ERR_BADARG;
+ old_msg->tp_state = errno;
}
- errno = EINVAL;
return old_msg;
}
errno = 0;
rmr_mbuf_t* msg;
if( (ctx = (uta_ctx_t *) vctx) == NULL ) {
+ errno = EINVAL;
if( old_msg != NULL ) {
old_msg->state = RMR_ERR_BADARG;
+ old_msg->tp_state = errno;
}
- errno = EINVAL;
return old_msg;
}
nready = epoll_wait( eps->ep_fd, eps->events, 1, ms_to ); // block until something or timedout
if( nready <= 0 ) { // we only wait on ours, so we assume ready means it's ours
msg->state = RMR_ERR_TIMEOUT;
+ msg->tp_state = errno;
} else {
return rcv_msg( ctx, msg ); // receive it and return it
}
int exp_len = 0; // length of expected ID
if( (ctx = (uta_ctx_t *) vctx) == NULL ) {
+ errno = EINVAL;
if( msg != NULL ) {
msg->state = RMR_ERR_BADARG;
+ msg->tp_state = errno;
}
- errno = EINVAL;
return msg;
}
errno = EINVAL;
if( mbuf ) {
mbuf->state = RMR_ERR_BADARG;
+ mbuf->tp_state = errno;
}
return mbuf;
}
errno = EINVAL;
if( mbuf != NULL ) {
mbuf->state = RMR_ERR_NOTSUPP;
+ mbuf->tp_state = errno;
}
return mbuf;
}
}
}
+ if( mbuf ) {
+ mbuf->tp_state = errno;
+ }
return mbuf;
}
long nano_sec; // max wait xlated to nano seconds
int state;
+ errno = EINVAL;
if( (ctx = (uta_ctx_t *) vctx) == NULL || mbuf == NULL ) {
- errno = EINVAL;
if( mbuf ) {
+ mbuf->tp_state = errno;
mbuf->state = RMR_ERR_BADARG;
}
return mbuf;
if( ! (ctx->flags & CFL_MTC_ENABLED) ) {
mbuf->state = RMR_ERR_NOTSUPP;
+ mbuf->tp_state = errno;
return mbuf;
}
if( call_id > MAX_CALL_ID || call_id < 2 ) { // 0 and 1 are reserved; user app cannot supply them
mbuf->state = RMR_ERR_BADARG;
+ mbuf->tp_state = errno;
return mbuf;
}
mbuf = mtosend_msg( ctx, mbuf, 0 ); // use internal function so as not to strip call-id; should be nil on success!
if( mbuf ) {
if( mbuf->state != RMR_OK ) {
+ mbuf->tp_state = errno;
return mbuf; // timeout or unable to connect or no endpoint are most likely issues
}
}
reuse. They have their reasons I guess. Thus, we will free
the old transport buffer if user passes the message in; at least
our mbuf will be reused.
+
+ When msg->state is not ok, this function must set tp_state in the message as some API
+ fucntions return the message directly and do not propigate errno into the message.
*/
static rmr_mbuf_t* rcv_msg( uta_ctx_t* ctx, rmr_mbuf_t* old_msg ) {
int state;
msg->state = nng_recvmsg( ctx->nn_sock, (nng_msg **) &msg->tp_buf, NO_FLAGS ); // blocks hard until received
if( (msg->state = xlate_nng_state( msg->state, RMR_ERR_RCVFAILED )) != RMR_OK ) {
+ msg->tp_state = errno;
return msg;
}
+ msg->tp_state = 0;
if( msg->tp_buf == NULL ) { // if state is good this _should_ not be nil, but parninoia says check anyway
msg->state = RMR_ERR_EMPTY;
+ msg->tp_state = 0;
return msg;
}
msg->mtype, msg->state, msg->len, msg->payload - (unsigned char *) msg->header );
} else {
msg->state = RMR_ERR_EMPTY;
+ msg->tp_state = 0;
msg->len = 0;
msg->alloc_len = rsize;
msg->payload = NULL;
Called by rmr_send_msg() and rmr_rts_msg(), etc. and thus we assume that all pointer
validation has been done prior.
+
+ When msg->state is not ok, this function must set tp_state in the message as some API
+ fucntions return the message directly and do not propigate errno into the message.
*/
static rmr_mbuf_t* send_msg( uta_ctx_t* ctx, rmr_mbuf_t* msg, nng_socket nn_sock, int retries ) {
int state;
// future: this should not happen as all buffers we deal with are zc buffers; might make sense to remove the test and else
msg->state = RMR_ERR_SENDFAILED;
errno = ENOTSUP;
+ msg->tp_state = errno;
return msg;
/*
NOT SUPPORTED
message type is used. If the initial lookup, with a subid, fails, then a
second lookup using just the mtype is tried.
+ When msg->state is not OK, this function must set tp_state in the message as
+ some API fucntions return the message directly and do not propigate errno into
+ the message.
+
CAUTION: this is a non-blocking send. If the message cannot be sent, then
it will return with an error and errno set to eagain. If the send is
a limited fanout, then the returned status is the status of the last
if( msg != NULL ) {
msg->state = RMR_ERR_BADARG;
errno = EINVAL; // must ensure it's not eagain
+ msg->tp_state = errno;
}
return msg;
}
fprintf( stderr, "rmr_send_msg: ERROR: message had no header\n" );
msg->state = RMR_ERR_NOHDR;
errno = EBADMSG; // must ensure it's not eagain
+ msg->tp_state = errno;
return msg;
}
msg->state = RMR_ERR_NOENDPT;
errno = ENXIO; // must ensure it's not eagain
+ msg->tp_state = errno;
return msg; // caller can resend (maybe) or free
}
msg->len = 100;
msg->mtype = 1;
msg->state = 999;
+ msg->tp_state = 999;
errno = 999;
msg = rmr_send_msg( rmc, msg );
errors += fail_if_nil( msg, "send_msg_ did not return a message on send " );
if( msg ) {
errors += fail_not_equal( msg->state, RMR_ERR_NOENDPT, "send_msg did not return no endpoints before rtable added " );
errors += fail_if( errno == 0, "send_msg did not set errno " );
+ errors += fail_if( msg->tp_state == 999, "send_msg did not set tp_state (1)" );
}
gen_rt( rmc ); // --- after this point there is a dummy route table so send and rts calls should be ok
msg->len = 100;
msg->mtype = 1;
msg->state = 999;
+ msg->tp_state = 999;
errno = 999;
msg = rmr_send_msg( rmc, msg );
errors += fail_if_nil( msg, "send_msg_ did not return a message on send " );
errors += fail_if( errno != 0, "send_msg set errno for send that should work " );
v = rmr_payload_size( msg );
errors += fail_if( v != 2048, "send_msg did not allocate new buffer with correct size " );
+ errors += fail_if( msg->tp_state == 999, "send_msg did not set tp_state (2)" );
}
rmr_set_stimeout( NULL, 0 );