Save PDO exceptions. (#24)

diff --git a/config.m4 b/config.m4
index 056b5a4..b4f9560 100644
--- a/config.m4
+++ b/config.m4
@@ -52,6 +52,8 @@
   cat >>Makefile.objects<< EOF
 all: cargo_build
 
+clean: cargo_clean
+
 cargo_build:
 	PHP_CONFIG=$PHP_PHP_CONFIG cargo build $CARGO_MODE_FLAGS
 	if [[ -f ./target/$CARGO_MODE_DIR/libskywalking_agent.dylib ]] ; then \\
@@ -59,7 +61,10 @@
 	if [[ -f ./target/$CARGO_MODE_DIR/libskywalking_agent.so ]] ; then \\
 		cp ./target/$CARGO_MODE_DIR/libskywalking_agent.so ./modules/skywalking_agent.so ; fi
 
-.PHONY: cargo_build
+cargo_clean:
+	cargo clean
+
+.PHONY: cargo_build cargo_clean
 EOF
 
   AC_CONFIG_LINKS([ \
diff --git a/src/channel.rs b/src/channel.rs
index 2172d23..eaf7097 100644
--- a/src/channel.rs
+++ b/src/channel.rs
@@ -33,7 +33,7 @@
 pub fn init_channel() -> anyhow::Result<()> {
     let (sender, receiver) = StdUnixStream::pair()?;
 
-    sender.set_nonblocking(true)?;
+    sender.set_nonblocking(false)?;
     receiver.set_nonblocking(true)?;
 
     if SENDER.set(Mutex::new(sender)).is_err() {
diff --git a/src/exception_frame.rs b/src/exception_frame.rs
new file mode 100644
index 0000000..612a98c
--- /dev/null
+++ b/src/exception_frame.rs
@@ -0,0 +1,40 @@
+// 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.
+
+use phper::sys;
+use std::marker;
+
+pub struct ExceptionFrame {
+    phantom: marker::PhantomData<isize>,
+}
+
+impl ExceptionFrame {
+    pub fn new() -> Self {
+        unsafe {
+            sys::zend_exception_save();
+        }
+        ExceptionFrame {
+            phantom: marker::PhantomData,
+        }
+    }
+}
+
+impl Drop for ExceptionFrame {
+    fn drop(&mut self) {
+        unsafe {
+            sys::zend_exception_restore();
+        }
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 4ffa85a..dd97960 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -20,6 +20,7 @@
 mod channel;
 mod component;
 mod context;
+mod exception_frame;
 mod execute;
 mod module;
 mod plugin;
diff --git a/src/plugin/plugin_pdo.rs b/src/plugin/plugin_pdo.rs
index cc43906..21205c9 100644
--- a/src/plugin/plugin_pdo.rs
+++ b/src/plugin/plugin_pdo.rs
@@ -17,6 +17,7 @@
 use crate::{
     component::COMPONENT_PHP_PDO_ID,
     context::RequestContext,
+    exception_frame::ExceptionFrame,
     execute::{get_this_mut, validate_num_args, AfterExecuteHook, BeforeExecuteHook, Noop},
 };
 use anyhow::Context;
@@ -206,6 +207,7 @@
 }
 
 fn after_hook_when_false(this: &mut ZObj, span: &mut Span) -> anyhow::Result<()> {
+    let _e = ExceptionFrame::new();
     let info = this.call("errorInfo", [])?;
     let info = info.as_z_arr().context("errorInfo isn't array")?;
 
diff --git a/tests/data/expected_context.yaml b/tests/data/expected_context.yaml
index 31910be..a05679e 100644
--- a/tests/data/expected_context.yaml
+++ b/tests/data/expected_context.yaml
@@ -390,6 +390,53 @@
                 key: db.statement,
                 value: "SELECT * FROM `mysql`.`user` WHERE `User` = :user",
               }
+          - operationName: PDO->prepare
+            parentSpanId: 0
+            spanId: 8
+            spanLayer: Database
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 8003
+            isError: false
+            spanType: Exit
+            peer: 127.0.0.1:3306
+            skipAnalysis: false
+            tags:
+              - { key: db.type, value: mysql }
+              - {
+                key: db.data_source,
+                value: "dbname=skywalking;host=127.0.0.1;port=3306",
+              }
+              - {
+                key: db.statement,
+                value: "SELECT * FROM not_exist",
+              }
+          - operationName: PDOStatement->execute
+            parentSpanId: 0
+            spanId: 9
+            spanLayer: Database
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 8003
+            isError: true
+            spanType: Exit
+            peer: 127.0.0.1:3306
+            skipAnalysis: false
+            tags:
+              - { key: db.type, value: mysql }
+              - {
+                key: db.data_source,
+                value: "dbname=skywalking;host=127.0.0.1;port=3306",
+              }
+              - {
+                key: db.statement,
+                value: "SELECT * FROM not_exist",
+              }
+            logs:
+              - logEvent:
+                - { key: SQLSTATE, value: 42S02 }
+                - { key: Error Code, value: '1146' }
+                - { key: Error, value: "Table 'skywalking.not_exist' doesn't exist" }
           - operationName: GET:/pdo.php
             parentSpanId: -1
             spanId: 0
diff --git a/tests/php/fpm/pdo.php b/tests/php/fpm/pdo.php
index d73d5c4..ac2ebe1 100644
--- a/tests/php/fpm/pdo.php
+++ b/tests/php/fpm/pdo.php
@@ -41,4 +41,13 @@
     Assert::same(count($rs), 0);
 }
 
+{
+    Assert::throws(function () {
+        $pdo = new PDO("mysql:dbname=skywalking;host=127.0.0.1;port=3306", "root", "password");
+        $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+        $sth = $pdo->prepare("SELECT * FROM not_exist");
+        $sth->execute();
+    }, PDOException::class);
+}
+
 echo "ok";