| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Hmssql.Connection.Backend
Description
What a connection's bytes travel on: a Backend is a connected byte
stream, as a record of operations. hmssql has three of its own (TCP,
and on Windows Shared Memory and Named Pipes), chosen by
connectProtocol. TLS, when the connection is
encrypted, runs on top of whichever it is.
On Linux and macOS TCP uses network's calls. On Windows all three use
hmssql's own overlapped IO, except under GHC's native IO manager with
a network and a GHC that support it, where TCP uses network's calls and
the pipes the manager (networkTcp).
The record is public so that a transport can be replaced or wrapped
without changing hmssql: ProtocolCustom connects
over a Backend the caller makes, which may be one of these with an
operation changed, a tunnel, or an in-memory stream for a test.
The pipe functions work on Windows only, since SQL Server listens on
pipes only there. On other platforms they throw an IOException
(pipesSupported says which it is).
Synopsis
- data Backend = Backend {
- backendRecv :: !(Ptr Word8 -> Int -> IO Int)
- backendSend :: !(ByteString -> IO ())
- backendSendMany :: !([[ByteString]] -> IO ())
- backendStopReceiving :: !(IO ())
- backendShutdown :: !(IO ())
- backendClose :: !(IO ())
- backendSocket :: !(Maybe Socket)
- tcpBackend :: String -> String -> IO Backend
- socketBackend :: Socket -> IO Backend
- pipeBackend :: FilePath -> IO Backend
- pipesSupported :: Bool
- sharedMemoryPipe :: String -> FilePath
- namedPipe :: Bool -> String -> String -> FilePath
- isLocalHost :: String -> IO Bool
- isDefaultInstance :: String -> Bool
- data PipeListener
- pipeListen :: FilePath -> IO PipeListener
- pipeAccept :: PipeListener -> IO (Backend, IO ())
- pipeListenerClose :: PipeListener -> IO ()
- networkTcp :: Bool
- tcpBackendWith :: (forall a. IO a -> IO a) -> String -> String -> IO Backend
- connectAddresses :: (forall a. IO a -> IO a) -> NonEmpty AddrInfo -> IO Socket
- addressConnectTimeout :: Int
- pipeBackendMasked :: FilePath -> IO Backend
- closeSocket :: Socket -> IO ()
- closeSocketWith :: (Socket -> IO ()) -> Socket -> IO ()
- socketCloser :: IORef (Socket -> IO ())
The record
A connected byte stream.
One thread receives at a time and one sends at a time, but a receive
and a send may be in progress together, and backendStopReceiving,
backendShutdown and backendClose are called from other threads
while they are.
An asynchronous exception should end a blocked backendRecv,
backendSend or backendSendMany promptly. hmssql doesn't use the
backend's stream afterwards: an interrupted send or receive ends the
connection. (Canceling a query never interrupts one of these: the
thread that is canceled waits for the connection's reader thread, and
the Attention is an ordinary send.) A backend that can't be
interrupted in some operation should say so; hmssql's own can be in
all of them, and network's except where socketBackend says.
The operations report failure by throwing IOException. They must
not refer to the Connection they serve, which
would keep a connection dropped without
close from ever being collected.
Constructors
| Backend | |
Fields
| |
TCP
tcpBackend :: String -> String -> IO Backend Source #
Connect over TCP to the host and port (a number or a service name).
The host's addresses are tried in the resolver's order until one
accepts; if none does, the last failure is thrown, an IOException.
An address that neither accepts nor refuses within
addressConnectTimeout is given up and the next one tried; the last
address alone waits as long as the operating system does. Nagle's
algorithm is turned off (TCP_NODELAY).
Interruptible. An exception that arrives just as it returns can lose
the backend, so call it masked (mask_ will do: the waits inside are
interruptible) and install the cleanup before unmasking, as bracket
does.
socketBackend :: Socket -> IO Backend Source #
A backend over a connected socket, which it owns from then on:
backendClose closes it.
It uses network's own calls on Linux and macOS, and on Windows under
GHC's native I/O manager when networkTcp says network supports that
manager. There a send of many pieces and a connect are blocking foreign
calls in network, which an asynchronous exception doesn't interrupt, and
which without -threaded stop every Haskell thread while they block.
Otherwise, on Windows, the socket is read and written as a handle, with
hmssql's own overlapped I/O, because network's calls block in a foreign
call there, where nothing can interrupt them; a program linked without
-threaded must then run with GHC's default I/O manager.
Shared Memory and Named Pipes
pipeBackend :: FilePath -> IO Backend Source #
Connect to a named pipe by its path, such as
\\.\pipe\SQLLocal\MSSQLSERVER (sharedMemoryPipe, namedPipe).
Throws an IOException if the pipe can't be opened; on a platform
without pipes, always.
The pipe is opened so that its server can identify the client but not
act as the client (SECURITY_IDENTIFICATION), the most restrictive
level SQL Server was seen to accept. A pipe on another machine is
reached over SMB, so Windows authenticates the user to that machine
first. Interruptible, except that without -threaded the open itself
stops the program while it waits; call it masked, as tcpBackend.
The pipes go through GHC's native I/O manager exactly when TCP goes
through network's calls (networkTcp), with or without -threaded, and
through hmssql's own overlapped I/O otherwise, where a program linked
without -threaded must run with GHC's default I/O manager.
pipesSupported :: Bool Source #
Whether this platform has Shared Memory and Named Pipes: Windows only.
sharedMemoryPipe :: String -> FilePath Source #
The pipe of the Shared Memory protocol for an instance on this
machine, by the instance's name: empty, or MSSQLSERVER, for the
default instance. SQL Server's Shared Memory protocol is TDS over
this local pipe.
namedPipe :: Bool -> String -> String -> FilePath Source #
The pipe of the Named Pipes protocol for an instance on a host, at the
path SQL Server uses unless it was configured with another. A host
that is this machine (isLocalHost, or a loopback address) gives the
local form, \\.\pipe\..., which doesn't go through the network.
isLocalHost :: String -> IO Bool Source #
Whether a host name means this machine: localhost, ., (local),
or the machine's own name, in any case. An IP address is not, even a
loopback one: Microsoft's clients connect to those over TCP.
isDefaultInstance :: String -> Bool Source #
Whether an instance name means the default instance: empty, or
MSSQLSERVER in any case.
Serving a pipe
data PipeListener Source #
The server side of a named pipe: for a mock server in tests, or a proxy.
pipeListen :: FilePath -> IO PipeListener Source #
Create a named pipe that clients on this machine can connect to, such
as \\.\pipe\my-test-pipe. No pipe of that name may exist yet.
Creating one needs no privilege. Its connections go through the same
I/O as pipeBackend's.
pipeAccept :: PipeListener -> IO (Backend, IO ()) Source #
Wait for a client: the connection to it, and an action that disconnects the client abruptly, discarding what it hasn't read yet (so that its next read or write fails, as after a TCP reset). The backend must still be closed. Interruptible; one thread accepts at a time.
pipeListenerClose :: PipeListener -> IO () Source #
Stop listening. Connections already accepted are not affected. It waits for an accept in progress, so stop the accepting thread first.
Internal
networkTcp :: Bool Source #
Whether TCP goes through network's own calls: on every platform but
Windows, and on Windows under GHC's native I/O manager, with or without
-threaded, when hmssql was built against network 3.3 or later with a
GHC whose native manager network supports. The pipes then go through
that manager too. Otherwise, on Windows, TCP and the pipes go through
hmssql's own overlapped I/O.
tcpBackendWith :: (forall a. IO a -> IO a) -> String -> String -> IO Backend Source #
tcpBackend, called masked, with the caller's restore for the parts
that wait: the lookup and each connect. Returns still masked.
connectAddresses :: (forall a. IO a -> IO a) -> NonEmpty AddrInfo -> IO Socket Source #
Connect to the first of the addresses that accepts, trying them in
the order given (the resolver's: a name such as localhost often
resolves to ::1 before 127.0.0.1, and the server may listen on only
one of them). Every address but the last is given
addressConnectTimeout; when it refuses or the time runs out, the
next is tried. The last address waits as long as the operating
system does, so a host with one address behaves as a plain connect,
and when it fails too, its failure is the one thrown. Called masked,
with the caller's restore for the wait of each connect; a caller's
timeout around the whole still bounds it, since the limit here
catches only its own expiry.
addressConnectTimeout :: Int Source #
How long an address that is not the host's last is given to accept
or refuse before the next address is tried: 4 seconds, in
microseconds. An address that refuses fails at once; one that is
silent (a firewall that drops the SYN, a host that is down, a stale
DNS record) fails only when the operating system gives up, after
about two minutes on Linux and 21 seconds on Windows, so without this
a silent first address cost that whole wait. Four seconds outlasts
one retransmission of the SYN on either platform, so a single lost
SYN doesn't skip a working address, and leaves a caller's 15-second
timeout room for a third address behind two silent ones.
pipeBackendMasked :: FilePath -> IO Backend Source #
pipeBackend, called masked.
closeSocket :: Socket -> IO () Source #
Close a socket, where a failure to close must not get in the way: in the
cleanup after a failed connect (where it would
replace the exception that made it fail), in close,
and in the finalizer of a connection dropped without it. It throws no
synchronous exception, and the descriptor is closed even when
close fails, which it can:
close marks the socket closed, then closes the descriptor with
GHC's closeFdWith, which first removes it from the IO manager. With
kqueue (macOS, BSD), that throws kevent: does not exist when the IO
manager's table has a registration for the descriptor number that the
kernel doesn't. Before GHC 9.14, a thread that starts to wait on a
descriptor just after another thread closed it leaves one (checked on
9.6 to 9.12), and the next socket to get the number then fails to close.
closeFdWith gives up before closing the descriptor (in 9.14 too),
which would stay open (and the connection with it) for the life of the
process, since the socket is already marked closed. So on failure the
descriptor is closed here directly: not through closeFdWith again,
which would block for good (after that failure it leaves the IO
manager's lock for the descriptor taken, and anything waiting on a
descriptor under the same lock hangs; nothing here can undo that).
Both are bugs in GHC's kqueue IO manager.
Runs masked, so that an asynchronous exception can't come between
close marking the socket closed and closing the descriptor.
closeSocketWith :: (Socket -> IO ()) -> Socket -> IO () Source #
closeSocket with the given way to close the socket, standing in for
close. If it fails after marking the socket closed (as
close does when closeFdWith fails), the descriptor is closed
directly; if it fails before, the socket is left as it is (network's
finalizer closes it). A synchronous exception is not rethrown.
socketCloser :: IORef (Socket -> IO ()) Source #
How closeSocket closes a socket: close. Tests replace it to
make closing fail.