暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

解析PostgreSQL源码:异常处理的PG_TRY、PG_CATCH、PG_RE_THROW

原创 liaju 2020-11-06
5393

在 PostgreSQL 中异常处理的主要代码包括 PG_TRY,PG_CATCH,PG_RE_THROW。

在 elog.h 代码中可以找到详细的描述:

/*----------
  * API for catching ereport(ERROR) exits.  Use these macros like so:
  *
  *      PG_TRY();
  *      {
  *          ... code that might throw ereport(ERROR) ...
  *      }
  *      PG_CATCH();
  *      {
  *          ... error recovery code ...
  *      }
  *      PG_END_TRY();
  *
  * (The braces are not actually necessary, but are recommended so that
  * pgindent will indent the construct nicely.)  The error recovery code
  * can either do PG_RE_THROW to propagate the error outwards, or do a
  * (sub)transaction abort. Failure to do so may leave the system in an
  * inconsistent state for further processing.
  *
  * For the common case that the error recovery code and the cleanup in the
  * normal code path are identical, the following can be used instead:
  *
  *      PG_TRY();
  *      {
  *          ... code that might throw ereport(ERROR) ...
  *      }
  *      PG_FINALLY();
  *      {
  *          ... cleanup code ...
  *      }
  *      PG_END_TRY();
  *
  * The cleanup code will be run in either case, and any error will be rethrown
  * afterwards.
  *
  * You cannot use both PG_CATCH() and PG_FINALLY() in the same
  * PG_TRY()/PG_END_TRY() block.
  *
  * Note: while the system will correctly propagate any new ereport(ERROR)
  * occurring in the recovery section, there is a small limit on the number
  * of levels this will work for.  It's best to keep the error recovery
  * section simple enough that it can't generate any new errors, at least
  * not before popping the error stack.
  *
  * Note: an ereport(FATAL) will not be caught by this construct; control will
  * exit straight through proc_exit().  Therefore, do NOT put any cleanup
  * of non-process-local resources into the error recovery section, at least
  * not without taking thought for what will happen during ereport(FATAL).
  * The PG_ENSURE_ERROR_CLEANUP macros provided by storage/ipc.h may be
  * helpful in such cases.
  *
  * Note: if a local variable of the function containing PG_TRY is modified
  * in the PG_TRY section and used in the PG_CATCH section, that variable
  * must be declared "volatile" for POSIX compliance.  This is not mere
  * pedantry; we have seen bugs from compilers improperly optimizing code
  * away when such a variable was not marked.  Beware that gcc's -Wclobbered
  * warnings are just about entirely useless for catching such oversights.
  *----------
  */
 #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)
复制

这其中几个关键宏共同作用,实现了类似于CPP的异常机制

#define PG_TRY()  \
       do { \
              sigjmp_buf *save_exception_stack = PG_exception_stack; \
              ErrorContextCallback *save_context_stack = error_context_stack; \
              sigjmp_buf local_sigjmp_buf; \
              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_END_TRY()  \
              } \
              PG_exception_stack = save_exception_stack; \
              error_context_stack = save_context_stack; \
       } while (0)
复制

在PG_TRY中, 可能会抛出(throw) ereport(ERROR) 按照往常的处理, 这部分就直接报错退出了, 但是在这种情形下由于利用 setjmp 设置了跳转上下文,所以在出现ERROR异常的时候会直接跳转到PG_CATCH那部分代码
ereport中的这个跳转是在 pg_re_throw 函数中利用 siglongjmp 来实现的

一般的erreport 和 elog 在 ERROR 级别下的机制:
观察 postgres.c 的这个地方(3868行):

if (sigsetjmp(local_sigjmp_buf, 1) != 0)

这说明当有异常的时候 PG 会跳转到这里, 相当于设置了一个savepoint 。

在 EmitErrorReport() 这个函数中会向 客户端发送异常或 报错

之后调用 AbortCurrentTransaction() 方法把当前的事务终止,之后重新进入事件循环

最后修改时间:2020-11-06 16:53:17
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论