Postgresql源码(118)elog/ereport报错跳转功能分析

1 日志接口

elog.c完成PG中日志的生产、记录工作,对外常用接口如下:

1.1 最常用的ereport和elog

ereport(ERROR,
			(errcode(ERRCODE_UNDEFINED_TABLE),
			 errmsg("relation \"%s\" does not exist",
			   relation->relname)));
elog(ERROR, "unexpected enrtype: %d", enrmd->enrtype);

其实都是在调用errstart和errfinish函数完成具体工作。

#define elog(elevel, ...)  \
	ereport(elevel, errmsg_internal(__VA_ARGS__))

#define ereport(elevel, ...)	\
	ereport_domain(elevel, TEXTDOMAIN, __VA_ARGS__)

#define ereport_domain(elevel, domain, ...)	\
	do { \
		pg_prevent_errno_in_scope(); \
		if (__builtin_constant_p(elevel) && (elevel) >= ERROR ? \
			errstart_cold(elevel, domain) : \
			errstart(elevel, domain)) \
			__VA_ARGS__, errfinish(__FILE__, __LINE__, __func__); \
		if (__builtin_constant_p(elevel) && (elevel) >= ERROR) \
			pg_unreachable(); \
	} while(0)

2 日志数据结构

  • 单条日志被抽象为ErrorData,记录了日志的全部信息。
  • elog.c提供了一个深度为5的stack来存放最多五层的ErrorData,大部分场景下,stack只会使用一层:
#define ERRORDATA_STACK_SIZE  5
static ErrorData errordata[ERRORDATA_STACK_SIZE];

在这里插入图片描述

3 日志的工作流程

每一条日志都是经过errstart和errfinish函数处理的,分工大致为:

  • errstart
    1. 在errdata堆栈中压入一个新的edata
    2. 为edata填充部分错误信息,例如错误等级、需要输出到server/client、按需升级错误等级(例如Crit区域中大于等于ERROR的需要变成PINIC)
  • errfinish
    1. 在进入errfinish前,错误信息的文本经过errmsg或errmsg_internal函数中的EVALUATE_MESSAGE宏记入edata中的message中,内存用的是errfinish
    2. 三部分工作:
      • 3.1 完成PG_TRY()、PG_CATCH()跳转功能,为errfinish添加try/catch功能
      • 3.2 完成error_context_stack的回调功能,为errfinish增加报错信息
      • 3.3 完成EmitErrorReport,为errfinish发送错误信息

3.1 完成PG_TRY()、PG_CATCH()跳转功能,为errfinish添加try/catch功能

注意PG_CATCH和PG_FINALLY是二选一的,区别是PG_FINALLY会在最后把异常重新抛出去,而PG_CATCH自己处理完了就不在向上抛了。

#define PG_TRY()  \
	do { \
		sigjmp_buf *_save_exception_stack = PG_exception_stack; \
		ErrorContextCallback *_save_context_stack = error_context_stack; \
		sigjmp_buf _local_sigjmp_buf; \
		bool _do_rethrow = false; \
		if (sigsetjmp(_local_sigjmp_buf, 0) == 0) \
		{ \
			PG_exception_stack = &_local_sigjmp_buf

#define PG_CATCH(...)	\
		} \
		else \
		{ \
			PG_exception_stack = _save_exception_stack; \
			error_context_stack = _save_context_stack

#define PG_FINALLY() \
		} \
		else \
			_do_rethrow = true; \
		{ \
			PG_exception_stack = _save_exception_stack; \
			error_context_stack = _save_context_stack

#define PG_END_TRY()  \
		} \
		if (_do_rethrow) \
				PG_RE_THROW(); \
		PG_exception_stack = _save_exception_stack; \
		error_context_stack = _save_context_stack; \
	} while (0)

我们先看一个PG中常规的报错流程,注意是不在PG_TRY中的elog(ERROR),发生>=ERROR级别的异常后,在errfinish中会抛出异常,jmp到PostgresMain函数中的sigsetjmp的位置,进入异常回收流程。
在这里插入图片描述
那么如果代码中的报错被PG_TRY、PG_CATCH包裹时,抛出异常会发生什么呢?
在这里插入图片描述

3.2 完成error_context_stack的回调功能,为errfinish增加报错信息

error_context_stack是一个Lisrt记录了回调函数回调函数的参数,这里的函数的作用是添加报错信息,因为这些函数会在errfinish时被调用,函数中往往都会使用errcontext_msg记录一些更详细的报错文本。

typedef struct ErrorContextCallback
{
	struct ErrorContextCallback *previous;
	void		(*callback) (void *arg);
	void	   *arg;
} ErrorContextCallback;

extern PGDLLIMPORT ErrorContextCallback *error_context_stack;

例如pl编译时配置的plpgsql_compile_error_callback函数,为了增加编译报错时,错误的发生的位置等:

do_compile
	...
	plerrcontext.callback = plpgsql_compile_error_callback;
	plerrcontext.arg = forValidator ? proc_source : NULL;
	plerrcontext.previous = error_context_stack;
	error_context_stack = &plerrcontext;
	...
static void
plpgsql_compile_error_callback(void *arg)
{
	if (arg)
	{
		/*
		 * Try to convert syntax error position to reference text of original
		 * CREATE FUNCTION or DO command.
		 */
		if (function_parse_error_transpose((const char *) arg))
			return;

		/*
		 * Done if a syntax error position was reported; otherwise we have to
		 * fall back to a "near line N" report.
		 */
	}

	if (plpgsql_error_funcname)
		errcontext("compilation of PL/pgSQL function \"%s\" near line %d",
				   plpgsql_error_funcname, plpgsql_latest_lineno());
}

error_context_stack和上面提到的PG_exception_stack都会在TRY中进行保存和恢复,为什么:

  • PG_exception_stack比较好理解,因为TRY时会建一个新的jmpbuf用PG_exception_stack指向,之前的旧值需要记录下来,TRY完了需要恢复。
  • error_context_stack的回调函数是在子模块中配置的,正常执行完子模块会把error_context_stack恢复原样,但一旦error发生了跳转,恢复逻辑就被跳过了。所以在TRY中自带了恢复error_context_stack的逻辑。

3.3 完成EmitErrorReport,为errfinish发送错误信息

errfinish
	...
	...
	EmitErrorReport
	...
	...

EmitErrorReport函数中会执行发送逻辑:

EmitErrorReport
	...
	if (edata->output_to_server && emit_log_hook)
		(*emit_log_hook) (edata);

	/* Send to server log, if enabled */
	if (edata->output_to_server)
		send_message_to_server_log(edata);    // 写 write_syslog

	/* Send to client, if enabled */
	if (edata->output_to_client)
		send_message_to_frontend(edata);      // 写libpq,发送到客户端或主进程

4 CopyErrorData / FlushErrorState / FreeErrorData

由于edata中的信息是在ErrorContext中申请的,在PG_CATCH时,是能拿到edata使用的,但在catch时不应该使用ErrorContext申请任何内存,所以惯用法是在PG_CATCH中用CopyErrorData把edata拷贝到当前的上下文中,在进行操作。然后把ErrorContext中的edata用FlushErrorState清理干净。

例如PL中的用法

		PG_CATCH();
		{
			ErrorData  *edata;
			...
			...
			/* Save error info in our stmt_mcontext */
			MemoryContextSwitchTo(stmt_mcontext);
			
			// 拷贝edata到当前上下文中
			edata = CopyErrorData();
			// ErrorContext中的用完尽快释放
			FlushErrorState();

			...
			...
			// 尽情使用使用edata
			exception_matches_conditions(edata, exception->conditions)
			...
			...	

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/263333.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何粗暴地下载huggingface_hub指定数据文件

参考这里: https://huggingface.co/docs/huggingface_hub/guides/download 可见下载单个文件,下载整个仓库文件都是可行的。 这是使用snapshot_download下载的一个例子: https://qq742971636.blog.csdn.net/article/details/135150482 sn…

轻松管理TXT文本,高效批量内容调整,打造高效工作流程!

在数字时代,文本文件已经成为我们生活和工作中不可或缺的一部分。无论是简单的笔记、待办事项,还是复杂的项目报告、小说草稿,TXT文本都能为我们提供灵活的存储和编辑方式。但是,随着文本文件的增多,如何轻松管理、高效…

Java 并发编程中的线程池

7 并发编程中的线程池 自定义线程池 package com.rainsun.d7_thread_pool;import lombok.extern.slf4j.Slf4j;import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Co…

vue3引入使用高德地图,不显示地图问题

将全局引入的mockjs去除,就可以了。

基于ChatGLM搭建专业领域问答机器人的思路

如果我们对ChatGLM进一步提出涉及专业领域的问题,而此方面知识是ChatGLM未经数据训练的,那么ChatGLM的回答效果如何呢?本节将考察ChatGLM在专业领域的问答水平,并尝试解决此方面的问题。 在使用ChatGLM制作专业领域问答机器人之前…

如何利用烛龙和谷歌插件优化CLS(累积布局偏移) | 京东云技术团队

简介 CLS 衡量的是页面的整个生命周期内发生的每次意外布局偏移的最大突发性_布局偏移分数_。布局变化的发生是因为浏览器倾向于异步加载页面元素。更重要的是,您的页面上可能存在一些初始尺寸未知的媒体元素。这种组合意味着浏览器在加载完成之前无法确定单个元素…

anconda常用命令

一、基础指令说明 1、查看anconda版本号 conda --version 2、查看当前已有虚拟环境 conda env list 3、创建新环境 conda create -n classify python3.9 创建一个叫做classify的虚拟环境,其中python等于3.9 4、进入虚拟环境 activate classify 5、安装包 接下来…

【Skynet 入门实战练习】事件模块 | 批处理模块 | GM 指令 | 模糊搜索

文章目录 前言事件模块批处理模块GM 指令模块模糊搜索最后 前言 本节完善了项目,实现了事件、批处理、模糊搜索模块、GM 指令模块。 事件模块 什么是事件模块?事件模块是用来在各系统之间传递事件消息的。 为什么需要事件模块?主要目的是…

由浅入深走进Python异步编程【多进程】(含代码实例讲解 || multiprocessing、异步进程池、进程通信)

写在前面 从底层到第三方库,全面讲解python的异步编程。这节讲述的是python的多线程实现,纯干货,无概念,代码实例讲解。 本系列有6章左右,点击头像或者专栏查看更多内容,陆续更新,欢迎关注。 …

群多多社群人脉H5-2.1.4多开插件+小程序独立前端+搭建教程

功能介绍: 1、群多多社群大全,是一个集发布、展示社群信息、人脉推广的裂变工具/平台。 2、通过人脉广场,将商家信息通过名片进行展示,让资源对接、人脉推广更加便捷高效。 3、行业群、兴趣群、知识付费群、交友群、商家活动推…

DMA实验3-外设到内存搬运

实验要求 使用 DMA 的方式将串口接收缓存寄存器的值搬运到内存中,同时闪烁 LED1 。 CubeMX 配置 DMA 配置: 串口中断配置 代码实现 如何判断串口接收是否完成?如何知道串口收到数据的长度? 使用串口空闲中断(IDL…

十一、W5100S/W5500+RP2040之MicroPython开发<MQTT阿里云示例>

文章目录 1. 前言2. 平台操作流程3. WIZnet以太网芯片4. 示例讲解以及使用4.1 程序流程图4.2 测试准备4.3 连接方式4.4 相关代码4.5 烧录验证 5. 注意事项6. 相关链接 1. 前言 在这个智能硬件和物联网时代,MicroPython和树莓派PICO正以其独特的优势引领着嵌入式开发…

谷歌 | Duet AI 让洞察、聚类模型和可视化变得简单

迷失在数据的海洋 我们都经历过这样的情况:淹没在数据的海洋中,努力驾驭复杂的管道,感觉数据令人头晕。管理大量充满不同工具和 Google 搜索的选项卡以及花费大量时间筛选数据和代码以创建满足您需求的模型所带来的挫败感,真的会…

探索UX设计师的日常任务,赶紧看看

UX 设计师专注于产品开发的各个方面,包括设计、可用性、功能、甚至品牌和营销。他们的工作涉及用户与产品交互的整个端到端旅程,包括为产品和业务识别新的机会。 鉴于他们广泛的范围,UX 设计师根据公司和项目的要求,执行多种不同…

探索 MajicStudio:一款多功能视频编辑软件

一、产品简介 MajicStudio是一款基于人工智能的图片编辑与设计工具,拥有简洁的界面与丰富功能。采用深度学习和计算机视觉技术可以自动识别图片要素。 二、应用场景 MajicStudio的AI图像功能适用于多场景,包括艺术设计、电商、游戏和文创等场景。 三…

【Proteus仿真】【Arduino单片机】蓝牙遥控小车

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器,使LCD1602液晶,L298电机,直流电机,HC05/06蓝牙模块等。 主要功能: 系统运行后,LCD1602…

数据结构和算法笔记2:二分法

二分法网上有两种写法&#xff0c;一种左闭右闭&#xff0c;一种左闭右开&#xff0c;个人习惯左闭右闭的写法&#xff0c; 有序数组查找数 这是标准二分法&#xff0c;对应力扣的704. 二分查找&#xff1a; 求值为target的索引 int search(vector<int>& nums, i…

Thread类的基本用法

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;JavaEE &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; Thread 1. 线程创建1.1 继承Thread类1.2 实…

arcEngine修改字段标注

修改字段标注 在arcEngine中&#xff0c;有时候需要修改图层要素的标注值&#xff0c;而且每个字段值对应了要修改的内容&#xff0c;如字段值”1“替换成”A“&#xff0c;字段值”2“替换成”B“等&#xff0c;这就需要在替换的图层中&#xff0c;遍历每个要素&#xff0c;查…

LeetCode 21 合并两个有序链表

题目描述 合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 [],…