+\f
+/* A JSON-RPC session with reconnection. */
+
+struct jsonrpc_session {
+ struct reconnect *reconnect;
+ struct jsonrpc *rpc;
+ struct stream *stream;
+ struct pstream *pstream;
+ int last_error;
+ unsigned int seqno;
+ uint8_t dscp;
+};
+
+/* Creates and returns a jsonrpc_session to 'name', which should be a string
+ * acceptable to stream_open() or pstream_open().
+ *
+ * If 'name' is an active connection method, e.g. "tcp:127.1.2.3", the new
+ * jsonrpc_session connects to 'name'. If 'retry' is true, then the new
+ * session connects and reconnects to 'name', with backoff. If 'retry' is
+ * false, the new session will only try to connect once and after a connection
+ * failure or a disconnection jsonrpc_session_is_alive() will return false for
+ * the new session.
+ *
+ * If 'name' is a passive connection method, e.g. "ptcp:", the new
+ * jsonrpc_session listens for connections to 'name'. It maintains at most one
+ * connection at any given time. Any new connection causes the previous one
+ * (if any) to be dropped. */
+struct jsonrpc_session *
+jsonrpc_session_open(const char *name, bool retry)
+{
+ struct jsonrpc_session *s;
+
+ s = xmalloc(sizeof *s);
+ s->reconnect = reconnect_create(time_msec());
+ reconnect_set_name(s->reconnect, name);
+ reconnect_enable(s->reconnect, time_msec());
+ s->rpc = NULL;
+ s->stream = NULL;
+ s->pstream = NULL;
+ s->seqno = 0;
+ s->dscp = 0;
+ s->last_error = 0;
+
+ if (!pstream_verify_name(name)) {
+ reconnect_set_passive(s->reconnect, true, time_msec());
+ } else if (!retry) {
+ reconnect_set_max_tries(s->reconnect, 1);
+ reconnect_set_backoff(s->reconnect, INT_MAX, INT_MAX);
+ }
+
+ if (!stream_or_pstream_needs_probes(name)) {
+ reconnect_set_probe_interval(s->reconnect, 0);
+ }
+
+ return s;
+}
+
+/* Creates and returns a jsonrpc_session that is initially connected to
+ * 'jsonrpc'. If the connection is dropped, it will not be reconnected.
+ *
+ * On the assumption that such connections are likely to be short-lived
+ * (e.g. from ovs-vsctl), informational logging for them is suppressed. */
+struct jsonrpc_session *
+jsonrpc_session_open_unreliably(struct jsonrpc *jsonrpc, uint8_t dscp)
+{
+ struct jsonrpc_session *s;
+
+ s = xmalloc(sizeof *s);
+ s->reconnect = reconnect_create(time_msec());
+ reconnect_set_quiet(s->reconnect, true);
+ reconnect_set_name(s->reconnect, jsonrpc_get_name(jsonrpc));
+ reconnect_set_max_tries(s->reconnect, 0);
+ reconnect_connected(s->reconnect, time_msec());
+ s->dscp = dscp;
+ s->rpc = jsonrpc;
+ s->stream = NULL;
+ s->pstream = NULL;
+ s->seqno = 0;
+
+ return s;
+}
+
+void
+jsonrpc_session_close(struct jsonrpc_session *s)
+{
+ if (s) {
+ jsonrpc_close(s->rpc);
+ reconnect_destroy(s->reconnect);
+ stream_close(s->stream);
+ pstream_close(s->pstream);
+ free(s);
+ }
+}
+
+static void
+jsonrpc_session_disconnect(struct jsonrpc_session *s)
+{
+ if (s->rpc) {
+ jsonrpc_error(s->rpc, EOF);
+ jsonrpc_close(s->rpc);
+ s->rpc = NULL;
+ s->seqno++;
+ } else if (s->stream) {
+ stream_close(s->stream);
+ s->stream = NULL;
+ s->seqno++;
+ }
+}
+
+static void
+jsonrpc_session_connect(struct jsonrpc_session *s)
+{
+ const char *name = reconnect_get_name(s->reconnect);
+ int error;
+
+ jsonrpc_session_disconnect(s);
+ if (!reconnect_is_passive(s->reconnect)) {
+ error = jsonrpc_stream_open(name, &s->stream, s->dscp);
+ if (!error) {
+ reconnect_connecting(s->reconnect, time_msec());
+ } else {
+ s->last_error = error;
+ }
+ } else {
+ error = s->pstream ? 0 : jsonrpc_pstream_open(name, &s->pstream,
+ s->dscp);
+ if (!error) {
+ reconnect_listening(s->reconnect, time_msec());
+ }
+ }
+
+ if (error) {
+ reconnect_connect_failed(s->reconnect, time_msec(), error);
+ }
+ s->seqno++;
+}
+
+void
+jsonrpc_session_run(struct jsonrpc_session *s)
+{
+ if (s->pstream) {
+ struct stream *stream;
+ int error;
+
+ error = pstream_accept(s->pstream, &stream);
+ if (!error) {
+ if (s->rpc || s->stream) {
+ VLOG_INFO_RL(&rl,
+ "%s: new connection replacing active connection",
+ reconnect_get_name(s->reconnect));
+ jsonrpc_session_disconnect(s);
+ }
+ reconnect_connected(s->reconnect, time_msec());
+ s->rpc = jsonrpc_open(stream);
+ } else if (error != EAGAIN) {
+ reconnect_listen_error(s->reconnect, time_msec(), error);
+ pstream_close(s->pstream);
+ s->pstream = NULL;
+ }
+ }
+
+ if (s->rpc) {
+ size_t backlog;
+ int error;
+
+ backlog = jsonrpc_get_backlog(s->rpc);
+ jsonrpc_run(s->rpc);
+ if (jsonrpc_get_backlog(s->rpc) < backlog) {
+ /* Data previously caught in a queue was successfully sent (or
+ * there's an error, which we'll catch below.)
+ *
+ * We don't count data that is successfully sent immediately as
+ * activity, because there's a lot of queuing downstream from us,
+ * which means that we can push a lot of data into a connection
+ * that has stalled and won't ever recover.
+ */
+ reconnect_activity(s->reconnect, time_msec());
+ }
+
+ error = jsonrpc_get_status(s->rpc);
+ if (error) {
+ reconnect_disconnected(s->reconnect, time_msec(), error);
+ jsonrpc_session_disconnect(s);
+ s->last_error = error;
+ }
+ } else if (s->stream) {
+ int error;
+
+ stream_run(s->stream);
+ error = stream_connect(s->stream);
+ if (!error) {
+ reconnect_connected(s->reconnect, time_msec());
+ s->rpc = jsonrpc_open(s->stream);
+ s->stream = NULL;
+ } else if (error != EAGAIN) {
+ reconnect_connect_failed(s->reconnect, time_msec(), error);
+ stream_close(s->stream);
+ s->stream = NULL;
+ }
+ }
+
+ switch (reconnect_run(s->reconnect, time_msec())) {
+ case RECONNECT_CONNECT:
+ jsonrpc_session_connect(s);
+ break;
+
+ case RECONNECT_DISCONNECT:
+ reconnect_disconnected(s->reconnect, time_msec(), 0);
+ jsonrpc_session_disconnect(s);
+ break;
+
+ case RECONNECT_PROBE:
+ if (s->rpc) {
+ struct json *params;
+ struct jsonrpc_msg *request;
+
+ params = json_array_create_empty();
+ request = jsonrpc_create_request("echo", params, NULL);
+ json_destroy(request->id);
+ request->id = json_string_create("echo");
+ jsonrpc_send(s->rpc, request);
+ }
+ break;
+ }
+}
+
+void
+jsonrpc_session_wait(struct jsonrpc_session *s)
+{
+ if (s->rpc) {
+ jsonrpc_wait(s->rpc);
+ } else if (s->stream) {
+ stream_run_wait(s->stream);
+ stream_connect_wait(s->stream);
+ }
+ if (s->pstream) {
+ pstream_wait(s->pstream);
+ }
+ reconnect_wait(s->reconnect, time_msec());
+}
+
+size_t
+jsonrpc_session_get_backlog(const struct jsonrpc_session *s)
+{
+ return s->rpc ? jsonrpc_get_backlog(s->rpc) : 0;
+}
+
+/* Always returns a pointer to a valid C string, assuming 's' was initialized
+ * correctly. */
+const char *
+jsonrpc_session_get_name(const struct jsonrpc_session *s)
+{
+ return reconnect_get_name(s->reconnect);
+}
+
+/* Always takes ownership of 'msg', regardless of success. */
+int
+jsonrpc_session_send(struct jsonrpc_session *s, struct jsonrpc_msg *msg)
+{
+ if (s->rpc) {
+ return jsonrpc_send(s->rpc, msg);
+ } else {
+ jsonrpc_msg_destroy(msg);
+ return ENOTCONN;
+ }
+}
+
+struct jsonrpc_msg *
+jsonrpc_session_recv(struct jsonrpc_session *s)
+{
+ if (s->rpc) {
+ unsigned int received_bytes;
+ struct jsonrpc_msg *msg;
+
+ received_bytes = jsonrpc_get_received_bytes(s->rpc);
+ jsonrpc_recv(s->rpc, &msg);
+ if (received_bytes != jsonrpc_get_received_bytes(s->rpc)) {
+ /* Data was successfully received.
+ *
+ * Previously we only counted receiving a full message as activity,
+ * but with large messages or a slow connection that policy could
+ * time out the session mid-message. */
+ reconnect_activity(s->reconnect, time_msec());
+ }
+
+ if (msg) {
+ if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) {
+ /* Echo request. Send reply. */
+ struct jsonrpc_msg *reply;
+
+ reply = jsonrpc_create_reply(json_clone(msg->params), msg->id);
+ jsonrpc_session_send(s, reply);
+ } else if (msg->type == JSONRPC_REPLY
+ && msg->id && msg->id->type == JSON_STRING
+ && !strcmp(msg->id->u.string, "echo")) {
+ /* It's a reply to our echo request. Suppress it. */
+ } else {
+ return msg;
+ }
+ jsonrpc_msg_destroy(msg);
+ }
+ }
+ return NULL;
+}
+
+void
+jsonrpc_session_recv_wait(struct jsonrpc_session *s)
+{
+ if (s->rpc) {
+ jsonrpc_recv_wait(s->rpc);
+ }
+}
+
+bool
+jsonrpc_session_is_alive(const struct jsonrpc_session *s)
+{
+ return s->rpc || s->stream || reconnect_get_max_tries(s->reconnect);
+}
+
+bool
+jsonrpc_session_is_connected(const struct jsonrpc_session *s)
+{
+ return s->rpc != NULL;
+}
+
+unsigned int
+jsonrpc_session_get_seqno(const struct jsonrpc_session *s)
+{
+ return s->seqno;
+}
+
+int
+jsonrpc_session_get_status(const struct jsonrpc_session *s)
+{
+ return s && s->rpc ? jsonrpc_get_status(s->rpc) : 0;
+}
+
+int
+jsonrpc_session_get_last_error(const struct jsonrpc_session *s)
+{
+ return s->last_error;
+}
+
+void
+jsonrpc_session_get_reconnect_stats(const struct jsonrpc_session *s,
+ struct reconnect_stats *stats)
+{
+ reconnect_get_stats(s->reconnect, time_msec(), stats);
+}
+
+void
+jsonrpc_session_enable_reconnect(struct jsonrpc_session *s)
+{
+ reconnect_set_max_tries(s->reconnect, UINT_MAX);
+ reconnect_set_backoff(s->reconnect, RECONNECT_DEFAULT_MIN_BACKOFF,
+ RECONNECT_DEFAULT_MAX_BACKOFF);
+}
+
+void
+jsonrpc_session_force_reconnect(struct jsonrpc_session *s)
+{
+ reconnect_force_reconnect(s->reconnect, time_msec());
+}
+
+void
+jsonrpc_session_set_max_backoff(struct jsonrpc_session *s, int max_backoff)
+{
+ reconnect_set_backoff(s->reconnect, 0, max_backoff);
+}
+
+void
+jsonrpc_session_set_probe_interval(struct jsonrpc_session *s,
+ int probe_interval)
+{
+ reconnect_set_probe_interval(s->reconnect, probe_interval);
+}
+
+void
+jsonrpc_session_set_dscp(struct jsonrpc_session *s,
+ uint8_t dscp)
+{
+ if (s->dscp != dscp) {
+ if (s->pstream) {
+ int error;
+
+ error = pstream_set_dscp(s->pstream, dscp);
+ if (error) {
+ VLOG_ERR("%s: failed set_dscp %s",
+ reconnect_get_name(s->reconnect),
+ ovs_strerror(error));
+ }
+ /*
+ * XXX race window between setting dscp to listening socket
+ * and accepting socket. accepted socket may have old dscp value.
+ * Ignore this race window for now.
+ */
+ }
+ s->dscp = dscp;
+ jsonrpc_session_force_reconnect(s);
+ }
+}