async def zmq_ipc_pipe_end(ctx, side, endpoint, *, serializer: Optional[Serializer]=None, initialize=True):
        """
        Returns a `ZmqPipeEnd` backed by an `ipc` connection; the endpoint must contain the scheme part.
        If both ends of the connection are created on the same thread/task, to avoid a deadlock,
        it's necessary to first create both ends, then initialize `'b'` then `'a'`, such as this:

            a = await zmq_ipc_pipe_end(ctx, 'a', endpoint, initialize=False)
            b = await zmq_ipc_pipe_end(ctx, 'b', endpoint)
            await a.initialize()

        In that case, prefer `zmq_ipc_pipe` for creating both ends.

        Side `'a'` will bind a `ROUTER` socket on the given endpoint;
        side `'b'` will connect a `DEALER` socket to the given endpoint.
        """

        if side == 'a':
            result = ZmqPipeEnd(ctx, zmq.ROUTER, endpoint, port=None, bind=True, serializer=serializer)
        elif side == 'b':
            result = ZmqPipeEnd(ctx, zmq.DEALER, endpoint, port=None, serializer=serializer)
        else:
            raise ValueError("side must be 'a' or 'b'")

        if initialize:
            await result.initialize()
        return result