blob: ac8044d2edfcb00a4a5f3131492e0d7ec5269cd4 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "router_core_private.h"
#include <qpid/dispatch/amqp.h>
#include <stdio.h>
static void qdr_link_deliver_CT(qdr_core_t *core, qdr_action_t *action, bool discard);
static void qdr_link_flow_CT(qdr_core_t *core, qdr_action_t *action, bool discard);
static void qdr_send_to_CT(qdr_core_t *core, qdr_action_t *action, bool discard);
static void qdr_update_delivery_CT(qdr_core_t *core, qdr_action_t *action, bool discard);
static void qdr_delete_delivery_CT(qdr_core_t *core, qdr_action_t *action, bool discard);
static void qdr_deliver_continue_CT(qdr_core_t *core, qdr_action_t *action, bool discard);
//==================================================================================
// Internal Functions
//==================================================================================
void qdr_delivery_read_extension_state(qdr_delivery_t *dlv, uint64_t disposition, pn_data_t* disposition_date, bool update_disposition);
void qdr_delivery_copy_extension_state(qdr_delivery_t *src, qdr_delivery_t *dest, bool update_disposition);
//==================================================================================
// Interface Functions
//==================================================================================
qdr_delivery_t *qdr_link_deliver(qdr_link_t *link, qd_message_t *msg, qd_iterator_t *ingress,
bool settled, qd_bitmask_t *link_exclusion)
{
qdr_action_t *action = qdr_action(qdr_link_deliver_CT, "link_deliver");
qdr_delivery_t *dlv = new_qdr_delivery_t();
ZERO(dlv);
sys_atomic_init(&dlv->ref_count, 1); // referenced by the action
dlv->link = link;
dlv->msg = msg;
dlv->to_addr = 0;
dlv->origin = ingress;
dlv->settled = settled;
dlv->presettled = settled;
dlv->link_exclusion = link_exclusion;
dlv->error = 0;
action->args.connection.delivery = dlv;
qdr_action_enqueue(link->core, action);
return dlv;
}
qdr_delivery_t *qdr_link_deliver_to(qdr_link_t *link, qd_message_t *msg,
qd_iterator_t *ingress, qd_iterator_t *addr,
bool settled, qd_bitmask_t *link_exclusion)
{
qdr_action_t *action = qdr_action(qdr_link_deliver_CT, "link_deliver");
qdr_delivery_t *dlv = new_qdr_delivery_t();
ZERO(dlv);
sys_atomic_init(&dlv->ref_count, 1); // referenced by the action
dlv->link = link;
dlv->msg = msg;
dlv->to_addr = addr;
dlv->origin = ingress;
dlv->settled = settled;
dlv->presettled = settled;
dlv->link_exclusion = link_exclusion;
dlv->error = 0;
action->args.connection.delivery = dlv;
qdr_action_enqueue(link->core, action);
return dlv;
}
qdr_delivery_t *qdr_link_deliver_to_routed_link(qdr_link_t *link, qd_message_t *msg, bool settled,
const uint8_t *tag, int tag_length,
uint64_t disposition, pn_data_t* disposition_data)
{
if (tag_length > 32)
return 0;
qdr_action_t *action = qdr_action(qdr_link_deliver_CT, "link_deliver");
qdr_delivery_t *dlv = new_qdr_delivery_t();
ZERO(dlv);
sys_atomic_init(&dlv->ref_count, 1); // referenced by the action
dlv->link = link;
dlv->msg = msg;
dlv->settled = settled;
dlv->presettled = settled;
dlv->error = 0;
qdr_delivery_read_extension_state(dlv, disposition, disposition_data, true);
action->args.connection.delivery = dlv;
action->args.connection.tag_length = tag_length;
memcpy(action->args.connection.tag, tag, tag_length);
qdr_action_enqueue(link->core, action);
return dlv;
}
qdr_delivery_t *qdr_deliver_continue(qdr_delivery_t *in_dlv)
{
qdr_action_t *action = qdr_action(qdr_deliver_continue_CT, "deliver_continue");
action->args.connection.delivery = in_dlv;
// This incref is for the action reference
qdr_delivery_incref(in_dlv);
qdr_action_enqueue(in_dlv->link->core, action);
return in_dlv;
}
int qdr_link_process_deliveries(qdr_core_t *core, qdr_link_t *link, int credit)
{
qdr_connection_t *conn = link->conn;
qdr_delivery_t *dlv;
bool drained = false;
int offer = -1;
bool settled = false;
bool send_complete = false;
int num_deliveries_completed = 0;
if (link->link_direction == QD_OUTGOING) {
while (credit > 0 && !drained) {
sys_mutex_lock(conn->work_lock);
dlv = DEQ_HEAD(link->undelivered);
sys_mutex_unlock(conn->work_lock);
if (dlv) {
settled = dlv->settled;
core->deliver_handler(core->user_context, link, dlv, settled);
sys_mutex_lock(conn->work_lock);
send_complete = qdr_delivery_send_complete(dlv);
if (send_complete) {
//
// The entire message has been sent. It is now the appropriate time to have the delivery removed
// from the head of the undelivered list and move it to the unsettled list if it is not settled.
//
DEQ_REMOVE_HEAD(link->undelivered);
num_deliveries_completed ++;
dlv->link_work = 0;
if (settled) {
dlv->where = QDR_DELIVERY_NOWHERE;
// This decref is for removing this settled delivery from the undelivered list
qdr_delivery_decref(core, dlv);
} else {
DEQ_INSERT_TAIL(link->unsettled, dlv);
dlv->where = QDR_DELIVERY_IN_UNSETTLED;
}
credit--;
link->credit_to_core--;
link->total_deliveries++;
offer = DEQ_SIZE(link->undelivered);
}
else {
//
// The message is still being received/sent.
// 1. We cannot remove the delivery from the undelivered list.
// This delivery needs to stay at the head of the undelivered list until the entire message has been sent out i.e other deliveries in the
// undelivered list have to wait before this entire large delivery is sent out
// 2. We need to call deliver_handler so any newly arrived bytes can be pushed out
// 3. We need to break out of this loop otherwise a thread will keep spinning in here until the entire message has been sent out.
//
sys_mutex_unlock(conn->work_lock);
//
// Note here that we are not incrementing num_deliveries_processed. Since this delivery is still coming in or still being sent out,
// we cannot consider this delivery as fully processed.
//
return num_deliveries_completed;
}
sys_mutex_unlock(conn->work_lock);
}
}
if (drained)
core->drained_handler(core->user_context, link);
else if (offer != -1)
core->offer_handler(core->user_context, link, offer);
}
return num_deliveries_completed;
}
void qdr_link_flow(qdr_core_t *core, qdr_link_t *link, int credit, bool drain_mode)
{
qdr_action_t *action = qdr_action(qdr_link_flow_CT, "link_flow");
//
// Compute the number of credits now available that we haven't yet given
// incrementally to the router core. i.e. convert absolute credit to
// incremental credit.
//
credit -= link->credit_to_core;
if (credit < 0)
credit = 0;
link->credit_to_core += credit;
action->args.connection.link = link;
action->args.connection.credit = credit;
action->args.connection.drain = drain_mode;
qdr_action_enqueue(core, action);
}
void qdr_send_to1(qdr_core_t *core, qd_message_t *msg, qd_iterator_t *addr, bool exclude_inprocess, bool control)
{
qdr_action_t *action = qdr_action(qdr_send_to_CT, "send_to");
action->args.io.address = qdr_field_from_iter(addr);
action->args.io.message = qd_message_copy(msg);
action->args.io.exclude_inprocess = exclude_inprocess;
action->args.io.control = control;
qdr_action_enqueue(core, action);
}
void qdr_send_to2(qdr_core_t *core, qd_message_t *msg, const char *addr, bool exclude_inprocess, bool control)
{
qdr_action_t *action = qdr_action(qdr_send_to_CT, "send_to");
action->args.io.address = qdr_field(addr);
action->args.io.message = qd_message_copy(msg);
action->args.io.exclude_inprocess = exclude_inprocess;
action->args.io.control = control;
qdr_action_enqueue(core, action);
}
void qdr_delivery_update_disposition(qdr_core_t *core, qdr_delivery_t *delivery, uint64_t disposition,
bool settled, qdr_error_t *error, pn_data_t *ext_state, bool ref_given)
{
qdr_action_t *action = qdr_action(qdr_update_delivery_CT, "update_delivery");
action->args.delivery.delivery = delivery;
action->args.delivery.disposition = disposition;
action->args.delivery.settled = settled;
action->args.delivery.error = error;
// handle delivery-state extensions e.g. declared, transactional-state
qdr_delivery_read_extension_state(delivery, disposition, ext_state, false);
//
// The delivery's ref_count must be incremented to protect its travels into the
// core thread. If the caller has given its reference to us, we can simply use
// the given ref rather than increment a new one.
//
if (!ref_given)
qdr_delivery_incref(delivery);
qdr_action_enqueue(core, action);
}
void qdr_delivery_set_context(qdr_delivery_t *delivery, void *context)
{
delivery->context = context;
}
void qdr_delivery_set_cleared_proton_ref(qdr_delivery_t *dlv, bool cleared_proton_ref)
{
dlv->cleared_proton_ref = cleared_proton_ref;
}
void *qdr_delivery_get_context(qdr_delivery_t *delivery)
{
return delivery->context;
}
bool qdr_delivery_send_complete(const qdr_delivery_t *delivery)
{
if (!delivery)
return false;
return qd_message_send_complete(delivery->msg);
}
bool qdr_delivery_tag_sent(const qdr_delivery_t *delivery)
{
if (!delivery)
return false;
return qd_message_tag_sent(delivery->msg);
}
void qdr_delivery_set_tag_sent(const qdr_delivery_t *delivery, bool tag_sent)
{
if (!delivery)
return;
qd_message_set_tag_sent(delivery->msg, tag_sent);
}
bool qdr_delivery_receive_complete(const qdr_delivery_t *delivery)
{
if (!delivery)
return false;
return qd_message_receive_complete(delivery->msg);
}
void qdr_delivery_incref(qdr_delivery_t *delivery)
{
sys_atomic_inc(&delivery->ref_count);
}
void qdr_delivery_decref(qdr_core_t *core, qdr_delivery_t *delivery)
{
uint32_t ref_count = sys_atomic_dec(&delivery->ref_count);
assert(ref_count > 0);
if (ref_count == 1) {
//
// The delivery deletion must occur inside the core thread.
// Queue up an action to do the work.
//
qdr_action_t *action = qdr_action(qdr_delete_delivery_CT, "delete_delivery");
action->args.delivery.delivery = delivery;
qdr_action_enqueue(core, action);
}
}
void qdr_delivery_tag(const qdr_delivery_t *delivery, const char **tag, int *length)
{
*tag = (const char*) delivery->tag;
*length = delivery->tag_length;
}
qd_message_t *qdr_delivery_message(const qdr_delivery_t *delivery)
{
if (!delivery)
return 0;
return delivery->msg;
}
qdr_error_t *qdr_delivery_error(const qdr_delivery_t *delivery)
{
return delivery->error;
}
//==================================================================================
// In-Thread Functions
//==================================================================================
void qdr_delivery_release_CT(qdr_core_t *core, qdr_delivery_t *dlv)
{
bool push = dlv->disposition != PN_RELEASED;
dlv->disposition = PN_RELEASED;
dlv->settled = true;
bool moved = qdr_delivery_settled_CT(core, dlv);
if (push || moved)
qdr_delivery_push_CT(core, dlv);
//
// Remove the unsettled reference
//
if (moved)
qdr_delivery_decref_CT(core, dlv);
}
void qdr_delivery_failed_CT(qdr_core_t *core, qdr_delivery_t *dlv)
{
bool push = dlv->disposition != PN_MODIFIED;
dlv->disposition = PN_MODIFIED;
dlv->settled = true;
bool moved = qdr_delivery_settled_CT(core, dlv);
if (push || moved)
qdr_delivery_push_CT(core, dlv);
//
// Remove the unsettled reference
//
if (moved)
qdr_delivery_decref_CT(core, dlv);
}
bool qdr_delivery_settled_CT(qdr_core_t *core, qdr_delivery_t *dlv)
{
//
// Remove a delivery from its unsettled list. Side effects include issuing
// replacement credit and visiting the link-quiescence algorithm
//
qdr_link_t *link = dlv->link;
qdr_connection_t *conn = link ? link->conn : 0;
bool moved = false;
if (!link || !conn)
return false;
//
// The lock needs to be acquired only for outgoing links
//
if (link->link_direction == QD_OUTGOING)
sys_mutex_lock(conn->work_lock);
if (dlv->where == QDR_DELIVERY_IN_UNSETTLED) {
DEQ_REMOVE(link->unsettled, dlv);
dlv->where = QDR_DELIVERY_NOWHERE;
moved = true;
}
if (link->link_direction == QD_OUTGOING)
sys_mutex_unlock(conn->work_lock);
if (dlv->tracking_addr) {
dlv->tracking_addr->outstanding_deliveries[dlv->tracking_addr_bit]--;
dlv->tracking_addr->tracked_deliveries--;
if (dlv->tracking_addr->tracked_deliveries == 0)
qdr_check_addr_CT(core, dlv->tracking_addr, false);
dlv->tracking_addr = 0;
}
//
// If this is an incoming link and it is not link-routed or inter-router, issue
// one replacement credit on the link. Note that credit on inter-router links is
// issued immediately even for unsettled deliveries.
//
if (moved && link->link_direction == QD_INCOMING &&
link->link_type != QD_LINK_ROUTER && !link->connected_link)
qdr_link_issue_credit_CT(core, link, 1, false);
return moved;
}
static void qdr_do_message_to_addr_free(qdr_core_t *core, qdr_general_work_t *work)
{
if (work->msg)
qd_message_free(work->msg);
if (work->on_message_context)
qd_iterator_free((qd_iterator_t *)work->on_message_context);
}
static void qdr_delete_delivery_internal_CT(qdr_core_t *core, qdr_delivery_t *delivery)
{
qdr_link_t *link = delivery->link;
if (delivery->msg || delivery->to_addr) {
qdr_general_work_t *work = qdr_general_work(qdr_do_message_to_addr_free);
work->msg = delivery->msg;
work->on_message_context = delivery->to_addr;
qdr_post_general_work_CT(core, work);
}
if (delivery->tracking_addr) {
delivery->tracking_addr->outstanding_deliveries[delivery->tracking_addr_bit]--;
delivery->tracking_addr->tracked_deliveries--;
if (delivery->tracking_addr->tracked_deliveries == 0)
qdr_check_addr_CT(core, delivery->tracking_addr, false);
delivery->tracking_addr = 0;
}
if (link) {
if (delivery->presettled)
link->presettled_deliveries++;
else if (delivery->disposition == PN_ACCEPTED)
link->accepted_deliveries++;
else if (delivery->disposition == PN_REJECTED)
link->rejected_deliveries++;
else if (delivery->disposition == PN_RELEASED)
link->released_deliveries++;
else if (delivery->disposition == PN_MODIFIED)
link->modified_deliveries++;
}
//
// Free all the peer qdr_delivery_ref_t references
//
qdr_delivery_ref_t *ref = DEQ_HEAD(delivery->peers);
while (ref) {
qdr_del_delivery_ref(&delivery->peers, ref);
ref = DEQ_HEAD(delivery->peers);
}
qd_bitmask_free(delivery->link_exclusion);
qdr_error_free(delivery->error);
free_qdr_delivery_t(delivery);
}
static bool qdr_delivery_has_peer_CT(qdr_delivery_t *dlv)
{
return dlv->peer || DEQ_SIZE(dlv->peers) > 0;
}
void qdr_delivery_link_peers_CT(qdr_delivery_t *in_dlv, qdr_delivery_t *out_dlv)
{
// If there is no delivery or a peer, we cannot link each other.
if (!in_dlv || !out_dlv)
return;
if (!qdr_delivery_has_peer_CT(in_dlv)) {
// This is the very first peer. Link them up.
assert(!out_dlv->peer);
in_dlv->peer = out_dlv;
}
else {
if (in_dlv->peer) {
// This is the first time we know that in_dlv is going to have more than one peer.
// There is already a peer in the in_dlv->peer pointer, move it into a list and zero it out.
qdr_add_delivery_ref_CT(&in_dlv->peers, in_dlv->peer);
// Zero out the peer pointer. Since there is more than one peer, this peer has been moved to the "peers" linked list.
// All peers will now reside in the peers linked list. No need to decref/incref here because you are transferring ownership.
in_dlv->peer = 0;
}
qdr_add_delivery_ref_CT(&in_dlv->peers, out_dlv);
}
out_dlv->peer = in_dlv;
qdr_delivery_incref(out_dlv);
qdr_delivery_incref(in_dlv);
}
void qdr_delivery_unlink_peers_CT(qdr_core_t *core, qdr_delivery_t *dlv, qdr_delivery_t *peer)
{
// If there is no delivery or a peer, we cannot proceed.
if (!dlv || !peer)
return;
if (dlv->peer) {
//
// This is the easy case. One delivery has only one peer. we can simply
// zero them out and directly decref.
//
assert(dlv->peer == peer);
dlv->peer = 0;
peer->peer = 0;
qdr_delivery_decref_CT(core, dlv);
qdr_delivery_decref_CT(core, peer);
}
else {
//
// The dlv has more than one peer. We are going to find the peer of dlv that match with the passed in peer
// and delete that peer.
//
qdr_delivery_ref_t *dlv_ref = DEQ_HEAD(dlv->peers);
while (dlv_ref) {
qdr_delivery_t * peer_dlv = dlv_ref->dlv;
if (peer_dlv == peer) {
if (peer->peer) {
peer->peer = 0;
qdr_delivery_decref_CT(core, dlv);
}
qdr_del_delivery_ref(&dlv->peers, dlv_ref);
qdr_delivery_decref_CT(core, peer);
break;
}
dlv_ref = DEQ_NEXT(dlv_ref);
}
}
}
qdr_delivery_t *qdr_delivery_first_peer_CT(qdr_delivery_t *dlv)
{
// What if there are no peers for this delivery?
if (!qdr_delivery_has_peer_CT(dlv))
return 0;
if (dlv->peer) {
// If there is a dlv->peer, it is the one and only peer.
return dlv->peer;
}
else {
// The delivery has more than one peer.
qdr_delivery_ref_t *dlv_ref = DEQ_HEAD(dlv->peers);
// Save the next peer to dlv->next_peer_ref so we can use it when somebody calls qdr_delivery_next_peer_CT
dlv->next_peer_ref = DEQ_NEXT(dlv_ref);
// Return the first peer.
return dlv_ref->dlv;
}
}
qdr_delivery_t *qdr_delivery_next_peer_CT(qdr_delivery_t *dlv)
{
if (dlv->peer) {
// There is no next_peer if there is only one peer. If there is a non-zero dlv->peer, it is the only peer
return 0;
}
else {
// There is more than one peer to this delivery.
qdr_delivery_ref_t *next_peer_ref = dlv->next_peer_ref;
if (next_peer_ref) {
// Save the next peer to dlv->next_peer_ref so we can use it when somebody calls qdr_delivery_next_peer_CT
dlv->next_peer_ref = DEQ_NEXT(dlv->next_peer_ref);
return next_peer_ref->dlv;
}
return 0;
}
}
void qdr_delivery_decref_CT(qdr_core_t *core, qdr_delivery_t *dlv)
{
uint32_t ref_count = sys_atomic_dec(&dlv->ref_count);
assert(ref_count > 0);
if (ref_count == 1)
qdr_delete_delivery_internal_CT(core, dlv);
}
static void qdr_link_flow_CT(qdr_core_t *core, qdr_action_t *action, bool discard)
{
if (discard)
return;
qdr_link_t *link = action->args.connection.link;
int credit = action->args.connection.credit;
bool drain = action->args.connection.drain;
bool activate = false;
bool drain_was_set = !link->drain_mode && drain;
qdr_link_work_t *work = 0;
link->drain_mode = drain;
//
// If this is an attach-routed link, propagate the flow data downrange.
// Note that the credit value is incremental.
//
if (link->connected_link) {
qdr_link_t *clink = link->connected_link;
if (clink->link_direction == QD_INCOMING)
qdr_link_issue_credit_CT(core, link->connected_link, credit, drain);
else {
work = new_qdr_link_work_t();
ZERO(work);
work->work_type = QDR_LINK_WORK_FLOW;
work->value = credit;
if (drain)
work->drain_action = QDR_LINK_WORK_DRAIN_ACTION_DRAINED;
qdr_link_enqueue_work_CT(core, clink, work);
}
return;
}
//
// Handle the replenishing of credit outbound
//
if (link->link_direction == QD_OUTGOING && (credit > 0 || drain_was_set)) {
if (drain_was_set) {
work = new_qdr_link_work_t();
ZERO(work);
work->work_type = QDR_LINK_WORK_FLOW;
work->drain_action = QDR_LINK_WORK_DRAIN_ACTION_DRAINED;
}
sys_mutex_lock(link->conn->work_lock);
if (work)
DEQ_INSERT_TAIL(link->work_list, work);
if (DEQ_SIZE(link->undelivered) > 0 || drain_was_set) {
qdr_add_link_ref(&link->conn->links_with_work, link, QDR_LINK_LIST_CLASS_WORK);
activate = true;
}
sys_mutex_unlock(link->conn->work_lock);
}
//
// Activate the connection if we have deliveries to send or drain mode was set.
//
if (activate)
qdr_connection_activate_CT(core, link->conn);
}
/**
* Return the number of outbound paths to destinations that this address has.
* Note that even if there are more than zero paths, the destination still may
* be unreachable (e.g. an rnode next hop with no link).
*/
static long qdr_addr_path_count_CT(qdr_address_t *addr)
{
return (long) DEQ_SIZE(addr->subscriptions) + (long) DEQ_SIZE(addr->rlinks) +
(long) qd_bitmask_cardinality(addr->rnodes);
}
static void qdr_link_forward_CT(qdr_core_t *core, qdr_link_t *link, qdr_delivery_t *dlv, qdr_address_t *addr)
{
bool receive_complete = qd_message_receive_complete(qdr_delivery_message(dlv));
if (addr && addr == link->owning_addr && qdr_addr_path_count_CT(addr) == 0) {
//
// We are trying to forward a delivery on an address that has no outbound paths
// AND the incoming link is targeted (not anonymous).
// If the owning_addr is a multicast addr and there are no outbound paths, we will release this delivery
// and replenish the credit.
// For non multicast addresses, put the delivery on the incoming link's undelivered list. Note that it is safe
// to do this because the undelivered list will be flushed once the number of
// paths transitions from zero to one.
//
// Use the action-reference as the reference for undelivered rather
// than decrementing and incrementing the delivery ref_count.
//
if (qdr_is_addr_treatment_multicast(link->owning_addr)) {
qdr_delivery_release_CT(core, dlv);
qdr_link_issue_credit_CT(core, link, 1, false);
}
else {
DEQ_INSERT_TAIL(link->undelivered, dlv);
dlv->where = QDR_DELIVERY_IN_UNDELIVERED;
}
return;
}
int fanout = 0;
if (addr) {
fanout = qdr_forward_message_CT(core, addr, dlv->msg, dlv, false, link->link_type == QD_LINK_CONTROL);
if (link->link_type != QD_LINK_CONTROL && link->link_type != QD_LINK_ROUTER)
addr->deliveries_ingress++;
link->total_deliveries++;
}
//
// There is no address that we can send this delivery to, which means the addr was not found in our hastable. This
// can be because there were no receivers or because the address was not defined in the config file.
// If the treatment for such addresses is set to be unavailable, we send back a rejected disposition and detach the link
//
else if (core->qd->default_treatment == QD_TREATMENT_UNAVAILABLE) {
dlv->disposition = PN_REJECTED;
dlv->error = qdr_error(QD_AMQP_COND_NOT_FOUND, "Deliveries cannot be sent to an unavailable address");
qdr_delivery_push_CT(core, dlv);
//
// We will not detach this link because this could be anonymous sender. We don't know
// which address the sender will be sending to next
// If this was not an anonymous sender, the initial attach would have been rejected if the target address was unavailable.
//
return;
}
if (fanout == 0) {
//
// Message was not delivered, drop the delivery.
//
// If the delivery is not settled, release it.
//
if (!dlv->settled) {
qdr_delivery_release_CT(core, dlv);
//
// Set the discard flag on the message only if the message is not completely received yet.
//
if (!receive_complete)
qd_message_set_discard(dlv->msg, true);
}
//
// Decrementing the delivery ref count for the action
//
qdr_delivery_decref_CT(core, dlv);
qdr_link_issue_credit_CT(core, link, 1, false);
} else if (fanout > 0) {
if (dlv->settled || qdr_is_addr_treatment_multicast(addr)) {
//
// The delivery is settled. Keep it off the unsettled list and issue
// replacement credit for it now.
//
qdr_link_issue_credit_CT(core, link, 1, false);
if (receive_complete) {
//
// This decref is for the action ref
//
qdr_delivery_decref_CT(core, dlv);
}
else {
//
// The message is still coming through since receive_complete is false. We have to put this delivery in the settled list.
// We need to do this because we have linked this delivery to a peer.
// If this connection goes down, we will have to unlink peer so that peer knows that its peer is not-existent anymore
// and need to tell the other side that the message has been aborted.
//
//
// Again, don't bother decrementing then incrementing the ref_count, we are still using the action ref count
//
DEQ_INSERT_TAIL(link->settled, dlv);
dlv->where = QDR_DELIVERY_IN_SETTLED;
}
} else {
//
// Again, don't bother decrementing then incrementing the ref_count
//
DEQ_INSERT_TAIL(link->unsettled, dlv);
dlv->where = QDR_DELIVERY_IN_UNSETTLED;
//
// If the delivery was received on an inter-router link, issue the credit
// now. We don't want to tie inter-router link flow control to unsettled
// deliveries because it increases the risk of credit starvation if there
// are many addresses sharing the link.
//
if (link->link_type == QD_LINK_ROUTER)
qdr_link_issue_credit_CT(core, link, 1, false);
}
}
}
static void qdr_link_deliver_CT(qdr_core_t *core, qdr_action_t *action, bool discard)
{
if (discard)
return;
qdr_delivery_t *dlv = action->args.connection.delivery;
qdr_link_t *link = dlv->link;
if (link->connected_link) {
//
// If this is an attach-routed link, put the delivery directly onto the peer link
//
qdr_delivery_t *peer = qdr_forward_new_delivery_CT(core, dlv, link->connected_link, dlv->msg);
qdr_delivery_copy_extension_state(dlv, peer, true);
//
// Copy the delivery tag. For link-routing, the delivery tag must be preserved.
//
peer->tag_length = action->args.connection.tag_length;
memcpy(peer->tag, action->args.connection.tag, peer->tag_length);
qdr_forward_deliver_CT(core, link->connected_link, peer);
link->total_deliveries++;
if (!dlv->settled) {
DEQ_INSERT_TAIL(link->unsettled, dlv);
dlv->where = QDR_DELIVERY_IN_UNSETTLED;
//
// Note, in this case the ref_count is left unchanged as we are transferring
// the action's reference to the unsettled list's reference.
//
} else {
//
// If the delivery is settled, decrement the ref_count on the delivery.
// This count was the owned-by-action count.
//
qdr_delivery_decref_CT(core, dlv);
}
return;
}
//
// NOTE: The link->undelivered list does not need to be protected by the
// connection's work lock for incoming links. This protection is only
// needed for outgoing links.
//
if (DEQ_IS_EMPTY(link->undelivered)) {
qdr_address_t *addr = link->owning_addr;
if (!addr && dlv->to_addr) {
qdr_connection_t *conn = link->conn;
if (conn && conn->tenant_space)
qd_iterator_annotate_space(dlv->to_addr, conn->tenant_space, conn->tenant_space_len);
qd_hash_retrieve(core->addr_hash, dlv->to_addr, (void**) &addr);
}
//
// Give the action reference to the qdr_link_forward function. Don't decref/incref.
//
qdr_link_forward_CT(core, link, dlv, addr);
} else {
//
// Take the action reference and use it for undelivered. Don't decref/incref.
//
DEQ_INSERT_TAIL(link->undelivered, dlv);
dlv->where = QDR_DELIVERY_IN_UNDELIVERED;
}
}
static void qdr_send_to_CT(qdr_core_t *core, qdr_action_t *action, bool discard)
{
qdr_field_t *addr_field = action->args.io.address;
qd_message_t *msg = action->args.io.message;
if (!discard) {
qdr_address_t *addr = 0;
qd_iterator_reset_view(addr_field->iterator, ITER_VIEW_ADDRESS_HASH);
qd_hash_retrieve(core->addr_hash, addr_field->iterator, (void**) &addr);
if (addr) {
//
// Forward the message. We don't care what the fanout count is.
//
(void) qdr_forward_message_CT(core, addr, msg, 0, action->args.io.exclude_inprocess,
action->args.io.control);
addr->deliveries_from_container++;
} else
qd_log(core->log, QD_LOG_DEBUG, "In-process send to an unknown address");
}
qdr_field_free(addr_field);
qd_message_free(msg);
}
static void qdr_update_delivery_CT(qdr_core_t *core, qdr_action_t *action, bool discard)
{
qdr_delivery_t *dlv = action->args.delivery.delivery;
qdr_delivery_t *peer = qdr_delivery_first_peer_CT(dlv);
bool push = false;
bool peer_moved = false;
bool dlv_moved = false;
uint64_t disp = action->args.delivery.disposition;
bool settled = action->args.delivery.settled;
qdr_error_t *error = action->args.delivery.error;
bool error_unassigned = true;
//
// Logic:
//
// If disposition has changed and there is a peer link, set the disposition of the peer
// If settled, the delivery must be unlinked and freed.
// If settled and there is a peer, the peer shall be settled and unlinked. It shall not
// be freed until the connection-side thread settles the PN delivery.
//
if (disp != dlv->disposition) {
//
// Disposition has changed, propagate the change to the peer delivery.
//
dlv->disposition = disp;
if (peer) {
peer->disposition = disp;
peer->error = error;
push = true;
error_unassigned = false;
qdr_delivery_copy_extension_state(dlv, peer, false);
}
}
if (settled) {
if (peer) {
peer->settled = true;
if (peer->link) {
peer_moved = qdr_delivery_settled_CT(core, peer);
if (peer_moved)
push = true;
}
qdr_delivery_unlink_peers_CT(core, dlv, peer);
}
if (dlv->link)
dlv_moved = qdr_delivery_settled_CT(core, dlv);
}
if (push)
qdr_delivery_push_CT(core, peer);
//
// Release the action reference, possibly freeing the delivery
//
qdr_delivery_decref_CT(core, dlv);
//
// Release the unsettled references if the deliveries were moved
//
if (dlv_moved)
qdr_delivery_decref_CT(core, dlv);
if (peer_moved)
qdr_delivery_decref_CT(core, peer);
if (error_unassigned)
qdr_error_free(error);
}
static void qdr_delete_delivery_CT(qdr_core_t *core, qdr_action_t *action, bool discard)
{
if (!discard)
qdr_delete_delivery_internal_CT(core, action->args.delivery.delivery);
}
static void qdr_deliver_continue_peers_CT(qdr_core_t *core, qdr_delivery_t *in_dlv)
{
qdr_delivery_t *peer = qdr_delivery_first_peer_CT(in_dlv);
while (peer) {
qdr_link_work_t *work = peer->link_work;
//
// Determines if the peer connection can be activated.
// For a large message, the peer delivery's link_work MUST be at the head of the peer link's work list. This link work is only removed
// after the streaming message has been sent.
//
if (work) {
sys_mutex_lock(peer->link->conn->work_lock);
if (work == DEQ_HEAD(peer->link->work_list)) {
qdr_add_link_ref(&peer->link->conn->links_with_work, peer->link, QDR_LINK_LIST_CLASS_WORK);
sys_mutex_unlock(peer->link->conn->work_lock);
//
// Activate the outgoing connection for later processing.
//
qdr_connection_activate_CT(core, peer->link->conn);
}
else {
sys_mutex_unlock(peer->link->conn->work_lock);
}
}
peer = qdr_delivery_next_peer_CT(in_dlv);
}
}
static void qdr_deliver_continue_CT(qdr_core_t *core, qdr_action_t *action, bool discard)
{
if (discard)
return;
qdr_delivery_t *in_dlv = action->args.connection.delivery;
// This decref is for the action reference
qdr_delivery_decref_CT(core, in_dlv);
//
// If it is already in the undelivered list or it has no peers, don't try to deliver this again.
//
if (in_dlv->where == QDR_DELIVERY_IN_UNDELIVERED || !qdr_delivery_has_peer_CT(in_dlv))
return;
qdr_deliver_continue_peers_CT(core, in_dlv);
if (qd_message_receive_complete(qdr_delivery_message(in_dlv))) {
//
// The entire message has now been received. Check to see if there are in process subscriptions that need to
// receive this message. in process subscriptions, at this time, can deal only with full messages.
//
qdr_subscription_t *sub = DEQ_HEAD(in_dlv->subscriptions);
while (sub) {
DEQ_REMOVE_HEAD(in_dlv->subscriptions);
qdr_forward_on_message_CT(core, sub, in_dlv ? in_dlv->link : 0, in_dlv->msg);
sub = DEQ_HEAD(in_dlv->subscriptions);
}
// This is a multicast delivery
if (qdr_is_addr_treatment_multicast(in_dlv->link->owning_addr)) {
assert(in_dlv->where == QDR_DELIVERY_IN_SETTLED);
//
// The router will settle on behalf of the receiver in the case of multicast and send out settled
// deliveries to the receivers.
//
in_dlv->disposition = PN_ACCEPTED;
qdr_delivery_push_CT(core, in_dlv);
//
// The in_dlv has one or more peers. These peers will have to be unlinked.
//
qdr_delivery_t *peer = qdr_delivery_first_peer_CT(in_dlv);
qdr_delivery_t *next_peer = 0;
while (peer) {
next_peer = qdr_delivery_next_peer_CT(in_dlv);
qdr_delivery_unlink_peers_CT(core, in_dlv, peer);
peer = next_peer;
}
// Remove the delivery from the settled list and decref the in_dlv.
in_dlv->where = QDR_DELIVERY_NOWHERE;
qdr_delivery_decref_CT(core, in_dlv); // This decref is for removing the delivery from the settled list.
DEQ_REMOVE(in_dlv->link->settled, in_dlv);
}
}
}
/**
* Add link-work to provide credit to the link in an IO thread
*/
void qdr_link_issue_credit_CT(qdr_core_t *core, qdr_link_t *link, int credit, bool drain)
{
assert(link->link_direction == QD_INCOMING);
bool drain_changed = link->drain_mode |= drain;
link->drain_mode = drain;
if (!drain_changed && credit == 0)
return;
if (credit > 0)
link->flow_started = true;
qdr_link_work_t *work = new_qdr_link_work_t();
ZERO(work);
work->work_type = QDR_LINK_WORK_FLOW;
work->value = credit;
if (drain_changed)
work->drain_action = drain ? QDR_LINK_WORK_DRAIN_ACTION_SET : QDR_LINK_WORK_DRAIN_ACTION_CLEAR;
qdr_link_enqueue_work_CT(core, link, work);
}
/**
* This function should be called after adding a new destination (subscription, local link,
* or remote node) to an address. If this address now has exactly one destination (i.e. it
* transitioned from unreachable to reachable), make sure any unstarted in-links are issued
* initial credit.
*
* Also, check the inlinks to see if there are undelivered messages. If so, drain them to
* the forwarder.
*/
void qdr_addr_start_inlinks_CT(qdr_core_t *core, qdr_address_t *addr)
{
//
// If there aren't any inlinks, there's no point in proceeding.
//
if (DEQ_SIZE(addr->inlinks) == 0)
return;
if (qdr_addr_path_count_CT(addr) == 1) {
qdr_link_ref_t *ref = DEQ_HEAD(addr->inlinks);
while (ref) {
qdr_link_t *link = ref->link;
//
// Issue credit to stalled links
//
if (!link->flow_started)
qdr_link_issue_credit_CT(core, link, link->capacity, false);
//
// Drain undelivered deliveries via the forwarder
//
if (DEQ_SIZE(link->undelivered) > 0) {
//
// Move all the undelivered to a local list in case not all can be delivered.
// We don't want to loop here forever putting the same messages on the undelivered
// list.
//
qdr_delivery_list_t deliveries;
DEQ_MOVE(link->undelivered, deliveries);
qdr_delivery_t *dlv = DEQ_HEAD(deliveries);
while (dlv) {
DEQ_REMOVE_HEAD(deliveries);
qdr_link_forward_CT(core, link, dlv, addr);
dlv = DEQ_HEAD(deliveries);
}
}
ref = DEQ_NEXT(ref);
}
}
}
void qdr_delivery_push_CT(qdr_core_t *core, qdr_delivery_t *dlv)
{
if (!dlv || !dlv->link)
return;
qdr_link_t *link = dlv->link;
bool activate = false;
sys_mutex_lock(link->conn->work_lock);
if (dlv->where != QDR_DELIVERY_IN_UNDELIVERED) {
qdr_delivery_incref(dlv);
qdr_add_delivery_ref_CT(&link->updated_deliveries, dlv);
qdr_add_link_ref(&link->conn->links_with_work, link, QDR_LINK_LIST_CLASS_WORK);
activate = true;
}
sys_mutex_unlock(link->conn->work_lock);
//
// Activate the connection
//
if (activate)
qdr_connection_activate_CT(core, link->conn);
}
pn_data_t* qdr_delivery_extension_state(qdr_delivery_t *delivery)
{
if (!delivery->extension_state) {
delivery->extension_state = pn_data(0);
}
pn_data_rewind(delivery->extension_state);
return delivery->extension_state;
}
void qdr_delivery_free_extension_state(qdr_delivery_t *delivery)
{
if (delivery->extension_state) {
pn_data_free(delivery->extension_state);
delivery->extension_state = 0;
}
}
void qdr_delivery_write_extension_state(qdr_delivery_t *dlv, pn_delivery_t* pdlv, bool update_disposition)
{
if (dlv->disposition > PN_MODIFIED) {
pn_data_copy(pn_disposition_data(pn_delivery_local(pdlv)), qdr_delivery_extension_state(dlv));
if (update_disposition) pn_delivery_update(pdlv, dlv->disposition);
qdr_delivery_free_extension_state(dlv);
}
}
void qdr_delivery_export_transfer_state(qdr_delivery_t *dlv, pn_delivery_t* pdlv)
{
qdr_delivery_write_extension_state(dlv, pdlv, true);
}
void qdr_delivery_export_disposition_state(qdr_delivery_t *dlv, pn_delivery_t* pdlv)
{
qdr_delivery_write_extension_state(dlv, pdlv, false);
}
void qdr_delivery_copy_extension_state(qdr_delivery_t *src, qdr_delivery_t *dest, bool update_diposition)
{
if (src->disposition > PN_MODIFIED) {
pn_data_copy(qdr_delivery_extension_state(dest), qdr_delivery_extension_state(src));
if (update_diposition) dest->disposition = src->disposition;
qdr_delivery_free_extension_state(src);
}
}
void qdr_delivery_read_extension_state(qdr_delivery_t *dlv, uint64_t disposition, pn_data_t* disposition_data, bool update_disposition)
{
if (disposition > PN_MODIFIED) {
pn_data_rewind(disposition_data);
pn_data_copy(qdr_delivery_extension_state(dlv), disposition_data);
if (update_disposition) dlv->disposition = disposition;
}
}