diff --git a/.github/workflows/debug.yml b/.github/workflows/debug.yml
new file mode 100644
index 00000000..769299be
--- /dev/null
+++ b/.github/workflows/debug.yml
@@ -0,0 +1,146 @@
+name: Debug
+
+on:
+  push:
+    branches:
+      - "debug"
+
+jobs:
+  build-wheels:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        python-version: [3.5, 3.6, 3.7, 3.8, 3.9]
+        os: [ubuntu-20.04]
+        arch: [aarch64]
+        exclude:
+          # Python 3.5 is unable to properly
+          # find the recent VS tooling
+          # https://bugs.python.org/issue30389
+          - os: windows-latest
+            python-version: 3.5
+          - os: windows-latest
+            arch: aarch64
+          - os: macos-latest
+            arch: aarch64
+
+    defaults:
+      run:
+        shell: bash
+
+    steps:
+    - uses: actions/checkout@v1
+      with:
+        fetch-depth: 50
+        submodules: true
+
+    - name: Set up QEMU
+      if: matrix.arch == 'aarch64'
+      uses: docker/setup-qemu-action@v1
+
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v1
+      with:
+        python-version: ${{ matrix.python-version }}
+
+    - name: Install Python Deps
+      run: |
+        python -m pip install --upgrade setuptools pip wheel
+
+    - name: Build Wheels (linux)
+      if: startsWith(matrix.os, 'ubuntu')
+      env:
+        PYTHON_VERSION: ${{ matrix.python-version }}
+        ARCH: ${{ matrix.arch }}
+      run: |
+        case "${ARCH}" in
+          x86_64)
+            mlimg=manylinux1_x86_64
+            ;;
+          aarch64)
+            mlimg=manylinux2014_aarch64
+            ;;
+          *)
+            echo "Unsupported wheel arch: ${ARCH}" >&2
+            exit 1
+            ;;
+        esac
+
+        docker run --rm \
+          -v "${GITHUB_WORKSPACE}":/github/workspace:rw \
+          --workdir=/github/workspace \
+          -e GITHUB_WORKSPACE=/github/workspace \
+          -e PYTHON_VERSION="${PYTHON_VERSION}" \
+          --entrypoint=/github/workspace/.github/workflows/build-manylinux-wheels.sh \
+          quay.io/pypa/${mlimg}
+
+    - name: Build Wheels (non-linux)
+      if: "!startsWith(matrix.os, 'ubuntu')"
+      run: |
+        make clean
+        python setup.py bdist_wheel
+
+    - name: Test Wheels (native)
+      if: |
+        !contains(github.event.pull_request.labels.*.name, 'skip wheel tests')
+        && matrix.arch == 'x86_64'
+      env:
+        OS: ${{ matrix.os }}
+      run: |
+        if [ "${OS}" = "windows-latest" ]; then
+          export PGINSTALLATION="${PGBIN}"
+        fi
+        "${GITHUB_WORKSPACE}/.github/workflows/test-wheels.sh"
+
+    - name: Test Wheels (emulated)
+      if: |
+        !contains(github.event.pull_request.labels.*.name, 'skip wheel tests')
+        && matrix.arch != 'x86_64'
+      env:
+        PYTHON_VERSION: ${{ matrix.python-version }}
+        PGVERSION: 13
+        DISTRO_NAME: focal
+        ARCH: ${{ matrix.arch }}
+      run: |
+        sudo env DISTRO_NAME="${DISTRO_NAME}" PGVERSION="${PGVERSION}" \
+          .github/workflows/install-postgres.sh
+        # Allow docker guest to connect to the database
+        echo "port = 5433" | \
+            sudo tee --append /etc/postgresql/${PGVERSION}/main/postgresql.conf
+        echo "listen_addresses = '*'" | \
+          sudo tee --append /etc/postgresql/${PGVERSION}/main/postgresql.conf
+        echo "host all all 172.17.0.0/16 trust" | \
+          sudo tee --append /etc/postgresql/${PGVERSION}/main/pg_hba.conf
+        if [ "${PGVERSION}" -ge "11" ]; then
+          # Disable JIT to avoid unpredictable timings in tests.
+          echo "jit = off" | \
+            sudo tee --append /etc/postgresql/${PGVERSION}/main/postgresql.conf
+        fi
+        sudo pg_ctlcluster ${PGVERSION} main restart
+
+        case "${ARCH}" in
+          aarch64)
+            img="docker.io/arm64v8/python:${PYTHON_VERSION}-buster"
+            ;;
+          *)
+            echo "Unsupported wheel arch: ${ARCH}" >&2
+            exit 1
+            ;;
+        esac
+
+        docker run --rm \
+          -v "${GITHUB_WORKSPACE}":/github/workspace:rw \
+          -e GITHUB_WORKSPACE=/github/workspace \
+          -e PYTHON_VERSION="${PYTHON_VERSION}" \
+          --workdir=/github/workspace/ \
+          ${img} \
+          /bin/bash -ex -c ' \
+            echo GITHUB_WORKSPACE=${GITHUB_WORKSPACE} >> /etc/environment \
+            && echo PGHOST=$(ip route | grep default | cut -f3 -d" " | uniq) \
+              >> /etc/environment \
+            && echo PGPORT=5433 >> /etc/environment \
+            && echo PGUSER=postgres >> /etc/environment \
+            && echo ENVIRON_FILE /etc/environment >> /etc/login.defs \
+            && useradd -m -s /bin/bash test \
+            && su -l test /github/workspace/.github/workflows/test-wheels.sh \
+          '
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 870d3551..e3e96732 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -295,7 +295,6 @@ jobs:
         release_name: v${{ steps.relver.outputs.version }}
         target: ${{ github.event.pull_request.base.ref }}
         body: ${{ github.event.pull_request.body }}
-        draft: true
 
     - run: |
         ls -al dist/
@@ -304,6 +303,6 @@ jobs:
       uses: pypa/gh-action-pypi-publish@master
       with:
         user: __token__
-        # password: ${{ secrets.PYPI_TOKEN }}
-        password: ${{ secrets.TEST_PYPI_TOKEN }}
-        repository_url: https://test.pypi.org/legacy/
+        password: ${{ secrets.PYPI_TOKEN }}
+        # password: ${{ secrets.TEST_PYPI_TOKEN }}
+        # repository_url: https://test.pypi.org/legacy/
diff --git a/asyncpg/_testbase/__init__.py b/asyncpg/_testbase/__init__.py
index ce7f827f..65dcba6c 100644
--- a/asyncpg/_testbase/__init__.py
+++ b/asyncpg/_testbase/__init__.py
@@ -425,6 +425,7 @@ def setUp(self):
         test_func = getattr(self, self._testMethodName).__func__
         opts = getattr(test_func, '__connect_options__', {})
         self.con = self.loop.run_until_complete(self.connect(**opts))
+        self.con._protocol._test = str(self.id())
         self.server_version = self.con.get_server_version()
 
     def tearDown(self):
diff --git a/asyncpg/_version.py b/asyncpg/_version.py
index 1d2d172d..c5fffb10 100644
--- a/asyncpg/_version.py
+++ b/asyncpg/_version.py
@@ -10,4 +10,4 @@
 # supported platforms, publish the packages on PyPI, merge the PR
 # to the target branch, create a Git tag pointing to the commit.
 
-__version__ = '0.22.0.dev0'
+__version__ = '0.22.0'
diff --git a/asyncpg/protocol/coreproto.pxd b/asyncpg/protocol/coreproto.pxd
index f21559b4..e502e1f9 100644
--- a/asyncpg/protocol/coreproto.pxd
+++ b/asyncpg/protocol/coreproto.pxd
@@ -153,6 +153,8 @@ cdef class CoreProtocol:
 
     cdef _push_result(self)
     cdef _reset_result(self)
+    cdef _get_result(self)
+    cdef _restore_result(self, tuple result)
     cdef _set_state(self, ProtocolState new_state)
 
     cdef _ensure_connected(self)
diff --git a/asyncpg/protocol/coreproto.pyx b/asyncpg/protocol/coreproto.pyx
index 12ebf6c6..95a9413b 100644
--- a/asyncpg/protocol/coreproto.pyx
+++ b/asyncpg/protocol/coreproto.pyx
@@ -724,6 +724,26 @@ cdef class CoreProtocol:
         self._execute_portal_name = None
         self._execute_stmt_name = None
 
+    cdef _get_result(self):
+        return (
+            self.result_type,
+            self.result,
+            self.result_param_desc,
+            self.result_row_desc,
+            self.result_status_msg,
+            self.result_execute_completed,
+        )
+
+    cdef _restore_result(self, tuple result):
+        (
+            self.result_type,
+            self.result,
+            self.result_param_desc,
+            self.result_row_desc,
+            self.result_status_msg,
+            self.result_execute_completed,
+        ) = result
+
     cdef _set_state(self, ProtocolState new_state):
         if new_state == PROTOCOL_IDLE:
             if self.state == PROTOCOL_FAILED:
diff --git a/asyncpg/protocol/protocol.pyx b/asyncpg/protocol/protocol.pyx
index 4df256e6..8487bff8 100644
--- a/asyncpg/protocol/protocol.pyx
+++ b/asyncpg/protocol/protocol.pyx
@@ -102,6 +102,9 @@ cdef class BaseProtocol(CoreProtocol):
         self.completed_callback = self._on_waiter_completed
 
         self.queries_count = 0
+        self.pending_results = collections.deque()
+
+        self._test = 'N/A'
 
         try:
             self.create_future = loop.create_future
@@ -360,6 +363,7 @@ cdef class BaseProtocol(CoreProtocol):
 
                 with timer:
                     buffer, done, status_msg = await waiter
+                    print(f'{self._test} copy_out() got result {len(buffer)} {done} {status_msg}')
 
                 # buffer will be empty if CopyDone was received apart from
                 # the last CopyData message.
@@ -585,6 +589,7 @@ cdef class BaseProtocol(CoreProtocol):
             # a ConnectionResetError will be thrown into the task.
             pass
         finally:
+            print(f'{self._test} close() waiter=None')
             self.waiter = None
         self.transport.abort()
 
@@ -643,6 +648,7 @@ cdef class BaseProtocol(CoreProtocol):
             if cause is not None:
                 exc.__cause__ = cause
             self.waiter.set_exception(exc)
+        print(f'{self._test} connection_lost() waiter=None')
         self.waiter = None
 
     cdef _set_server_parameter(self, name, val):
@@ -705,6 +711,8 @@ cdef class BaseProtocol(CoreProtocol):
                     raise apg_exc.InternalClientError(
                         'waiter is not done while handling critical '
                         'protocol error')
+
+                print(f'{self._test} coreproto_error() waiter=None')
                 self.waiter = None
         finally:
             self.abort()
@@ -713,12 +721,15 @@ cdef class BaseProtocol(CoreProtocol):
         if self.waiter is not None:
             raise apg_exc.InterfaceError(
                 'cannot perform operation: another operation is in progress')
-        self.waiter = self.create_future()
+        self.waiter = waiter = self.create_future()
         if timeout is not None:
             self.timeout_handle = self.loop.call_later(
                 timeout, self.timeout_callback, self.waiter)
         self.waiter.add_done_callback(self.completed_callback)
-        return self.waiter
+        if self.pending_results:
+            self._restore_result(self.pending_results.popleft())
+            self._on_result()
+        return waiter
 
     cdef _on_result__connect(self, object waiter):
         waiter.set_result(True)
@@ -780,6 +791,11 @@ cdef class BaseProtocol(CoreProtocol):
 
     cdef _dispatch_result(self):
         waiter = self.waiter
+
+        if waiter is None:
+            self.pending_results.append(self._get_result())
+            return
+
         self.waiter = None
 
         if PG_DEBUG:
@@ -859,6 +875,7 @@ cdef class BaseProtocol(CoreProtocol):
                 self.cancel_waiter.set_result(None)
             self.cancel_waiter = None
             if self.waiter is not None and self.waiter.done():
+                print(f'{self._test} on_result() with cancel_waiter waiter=None')
                 self.waiter = None
             if self.waiter is None:
                 return
@@ -889,6 +906,7 @@ cdef class BaseProtocol(CoreProtocol):
                     self.waiter.set_result(None)
                 else:
                     self.waiter.set_exception(exc)
+            print(f'{self._test} on_connection_lost() closing waiter=None')
             self.waiter = None
         else:
             # The connection was lost because it was
@@ -911,6 +929,7 @@ cdef class BaseProtocol(CoreProtocol):
     # asyncio callbacks:
 
     def data_received(self, data):
+        print(f'{self._test} data received', len(data))
         self.buffer.feed_data(data)
         self._read_server_messages()
 
diff --git a/tests/__init__.py b/tests/__init__.py
index 6282ebe5..c412aff7 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -18,6 +18,6 @@ def suite():
 
 
 if __name__ == '__main__':
-    runner = unittest.runner.TextTestRunner()
+    runner = unittest.runner.TextTestRunner(verbosity=2)
     result = runner.run(suite())
     sys.exit(not result.wasSuccessful())