PostgreSQL数据库网络层——libpq Canceling Requests in Progress

本文详细介绍了PostgreSQL查询取消的过程。当前端请求取消查询时,它通过创建新的连接并发送CancelRequest消息,而非直接在原有连接上操作。服务器在接收到匹配的PID和密钥后会中断当前查询并发送错误消息。然而,取消请求并不保证一定能成功,因为它可能在查询执行完毕后到达。这一机制确保了系统的安全性和效率,但也带来了不确定性,前端必须等待后端响应以确认查询状态。
摘要由CSDN通过智能技术生成

POSTGRESQL手册描述该特性:在处理查询期间,前端可能会请求取消查询。出于执行效率的原因,取消请求不会在打开的连接上直接发送到后端:我们不希望后端在查询处理期间不断检查来自前端的新输入。取消请求应该相对不频繁,因此我们将它们稍微繁琐一些,以避免在正常情况下受到惩罚。为了发出取消请求,前端打开与服务器的新连接并发送 CancelRequest 消息,而不是通常通过新连接发送的 StartupMessage 消息。服务器将处理此请求,然后关闭连接。出于安全原因,没有直接回复取消请求消息。CancelRequest 消息将被忽略,除非它包含在连接启动期间传递给前端的相同密钥数据(PID 和密钥)。如果请求与当前正在执行的后端的 PID 和密钥匹配,则中止当前查询的处理。 (在现有实现中,这是通过向正在处理查询的后端进程发送一个特殊信号来完成的。)取消信号可能会或可能不会有任何影响——例如,如果它在后端完成处理查询之后到达,那么它将没有任何影响。如果取消有效,则会导致当前命令提前终止并显示错误消息。这一切的结果是,出于安全和效率的考虑,前端无法直接判断取消请求是否成功。它必须继续等待后端响应查询。发出取消只是提高了当前查询很快完成的几率,并提高了它失败并显示错误消息而不是成功的几率。由于取消请求是通过与服务器的新连接而不是通过常规的前端/后端通信链路发送的,因此取消请求可能由任何进程发出,而不仅仅是要取消其查询的前端。这可能会在构建多进程应用程序时提供额外的灵活性。它还引入了安全风险,因为未经授权的人可能会尝试取消查询。通过要求在取消请求中提供动态生成的密钥来解决安全风险。

取消请求并不是通过当前正在处理请求的连接发送的,而是会创建一个新的连接,创建该连接发送的消息与之前创建连接的消息不同,不再发送 startup 消息,而是发送一个 CancelReqeust 消息,该消息同样没有消息类型字段。首先我们知道正常后端进程在 startup 阶段,服务端还会给客户端发送一个 BackendKeyData 消息,该消息中包含服务端的进程 ID 和一个取消码(MyCancelKey)。如果客户端想取消当前正在执行的请求,则可以发送一个 CancelRequset 消息,该消息中包括 startup 阶段服务端提供的进程 ID 和取消码。需要注意的是,取消请求不保证一定成功,可能服务端接收到取消请求时,当前的查询请求已经结束。取消请求只能在一定程度上加速当前查询结束,如果当前请求被取消,客户端会收到一条错误消息。
在这里插入图片描述
后端处理流程在ProcessStartupPacket函数中,如下所示。而前端发送CancelReqeust 消息是在internal_cancel函数中进行的。

	/* The first field is either a protocol version number or a special request code. */
	port->proto = proto = pg_ntoh32(*((ProtocolVersion *) buf));
	if (proto == CANCEL_REQUEST_CODE)
	{
		processCancelRequest(port, buf);
		/* Not really an error, but we don't want to proceed further */
		return STATUS_ERROR;
	}

processCancelRequest函数是后端对BackendKeyData 消息包的处理函数,先获取需要cancel的后端pid、cancelAuthCode。我们知道所以后端进程的Backend都存放在BackendList双向链表中(后续会有博客详细分析哪些进程会在BackendList双向链表中有Backend节点)。首先循环遍历链表,匹配到需要cancel的后端pid、cancelAuthCode(cancel_key),如果匹配到调用signal_child函数向其发送SIGINT信号;如果未匹配到则打log即可。

/* The client has sent a cancel request packet, not a normal start-a-new-connection packet.  Perform the necessary processing. Nothing is sent back to the client */
static void processCancelRequest(Port *port, void *pkt) {
	CancelRequestPacket *canc = (CancelRequestPacket *) pkt;
	int			backendPID;
	int32		cancelAuthCode;
	Backend    *bp;
	dlist_iter	iter;

	backendPID = (int) pg_ntoh32(canc->backendPID);
	cancelAuthCode = (int32) pg_ntoh32(canc->cancelAuthCode);

	/* See if we have a matching backend.  In the EXEC_BACKEND case, we can no longer access the postmaster's own backend list, and must rely on the duplicate array in shared memory. */
	dlist_foreach(iter, &BackendList) {
		bp = dlist_container(Backend, elem, iter.cur);
		if (bp->pid == backendPID) { // 匹配到后端进程pid
			if (bp->cancel_key == cancelAuthCode) {
				/* Found a match; signal that backend to cancel current op */
				ereport(DEBUG2,(errmsg_internal("processing cancel request: sending SIGINT to process %d", backendPID)));
				signal_child(bp->pid, SIGINT);
			}else /* Right PID, wrong key: no way, Jose */
				ereport(LOG,(errmsg("wrong key in cancel request for process %d", backendPID)));
			return;
		}
	}

	/* No matching backend */
	ereport(LOG,(errmsg("PID %d in cancel request did not match any process",backendPID)));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值