| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Hmssql.Connection.Internal
Description
Connection state, packet I/O, and the incremental token reader.
Everything here is protected by the connection's reentrant mutex. A request
creates an ActiveResponse; tokens are pulled from it one at a time, and
starting a new request first cancels and drains any unfinished response so
the connection can never be left mid-message.
A request and its response travel on a Session. A connection has one,
connPrimary, unless it negotiated MARS (connMars): then each request
takes an SMP session of its own (Hmssql.Tds.Smp), so several responses
can be open at once, and a new request leaves the others alone. The
backend, the transport, the protocol state (connContext, the transaction
descriptor included), the mutex and the broken flag stay the
connection's: the sessions are one SQL session on the server.
Interruptions (asynchronous exceptions) are handled by two rules:
- A send that doesn't complete breaks the connection (
sendBytes): the server may hold part of a message, and nothing here tries to recover from that. - A read that is interrupted leaves the connection usable (
nextToken): an Attention is sent at once, which cancels the statement, and the next request drains the canceled response up to the server's acknowledgement (finishResponse). If that drain is itself interrupted, the connection is broken too.
A Connection value is a handle, a lease on the connection: every field
but connLease is shared or immutable, so a record copy is another handle
to the same connection. release invalidates the
handle it was given (invalidateLease) and renewLease makes a new one;
everything that takes the lock, and every row a stream yields, checks the
handle first (checkLease).
Applications use Hmssql.Connection; nothing here is stable.
Synopsis
- data Connection = Connection {
- connBackend :: !Backend
- connTransport :: !(IORef Transport)
- connPrimary :: !Session
- connMars :: !(Maybe Mars)
- connAlive :: !(IORef ())
- connIncoming :: !(TMVar ByteString)
- connEnded :: !(TVar (Maybe SomeException))
- connSendLock :: !(MVar ())
- connSendGather :: !SendGather
- connReader :: !(IORef (Maybe (Weak ThreadId)))
- connContext :: !(IORef TdsContext)
- connMutex :: !Mutex
- connBroken :: !(IORef (Maybe UnusableReason))
- connResetPending :: !(IORef Bool)
- connPinReader :: !Bool
- connSessionSetup :: ![Text]
- connValueAllocAhead :: !Int
- connOnInfo :: !(ServerMessage -> IO ())
- connOnEnvChange :: !(EnvChange -> IO ())
- connInSource :: !(IORef Bool)
- connInCallback :: !(IORef Bool)
- connLease :: !Int
- connCurrentLease :: !(IORef Int)
- data Transport
- data Session = Session {
- sessResponse :: !(IORef (Maybe ActiveResponse))
- sessGeneration :: !(IORef Int)
- sessRecvBuffer :: !(IORef [ByteString])
- sessSmp :: !(Maybe SmpSession)
- data SmpSession = SmpSession {
- smpSid :: !Word16
- smpLink :: !SmpLink
- smpSendSeq :: !(IORef Word32)
- smpAdvertised :: !(IORef Word32)
- data SmpLink = SmpLink {
- linkIncoming :: !(TQueue ByteString)
- linkSendHighWater :: !(TVar Word32)
- linkRecvSeq :: !(TVar Word32)
- linkRecvHighWater :: !(TVar Word32)
- linkEoms :: !(TVar Int)
- linkClosing :: !(TVar Bool)
- data Mars = Mars {
- marsRegistry :: !(TVar (IntMap SmpLink))
- marsOpen :: !(IORef (IntMap Session))
- marsReserved :: !(IORef (Maybe Session))
- marsWindow :: !Word32
- marsResetInFlight :: !(IORef (Maybe ActiveResponse))
- data ActiveResponse = ActiveResponse {
- arGeneration :: !Int
- arSession :: !Session
- arStatement :: !(Maybe Text)
- arEomBase :: !Int
- arCarriedReset :: !Bool
- arSawResetAck :: !(IORef Bool)
- arInConsumer :: !(IORef Bool)
- arCancellation :: !(IORef Cancellation)
- arReplay :: !(IORef [Token])
- arState :: !(IORef ReadState)
- arResultSet :: !(IORef (Maybe ResultSetInfo))
- arSawFinalDone :: !(IORef Bool)
- data Cancellation
- data ReadState = ReadState {}
- data ReadPosition
- = AtToken !ByteString
- | Reading (Stream Await IO ReadEvent)
- | Parked !ByteString (Parser ())
- newConnection :: Backend -> TdsContext -> (ServerMessage -> IO ()) -> IO Connection
- startReader :: Connection -> IO ()
- enableMars :: Connection -> Int -> IO Connection
- allSessions :: Connection -> IO [Session]
- closeConnection :: Connection -> IO () -> IO ()
- releaseTransport :: Connection -> IO ()
- closeSocket :: Socket -> IO ()
- closeSocketWith :: (Socket -> IO ()) -> Socket -> IO ()
- socketCloser :: IORef (Socket -> IO ())
- connectionContext :: Connection -> IO TdsContext
- connectionMars :: Connection -> Bool
- withConnectionLock :: Connection -> IO a -> IO a
- checkLease :: Connection -> IO ()
- invalidateLease :: Connection -> IO ()
- renewLease :: Connection -> IO Connection
- renewUsable :: Connection -> IO Connection
- checkNotBroken :: Connection -> IO ()
- refuseUnusable :: Connection -> IO ()
- currentUnusableReason :: Connection -> IO (Maybe UnusableReason)
- markBroken :: Connection -> UnusableReason -> IO ()
- reasonFor :: (SomeException -> UnusableReason) -> SomeException -> UnusableReason
- resetFailure :: SomeException -> UnusableReason
- throwSevere :: Connection -> Done -> Maybe Text -> IO a
- transportError :: SomeException -> SomeException
- recvSome :: Connection -> IO ByteString
- recvExactly :: Connection -> Int -> IO ByteString
- newBufferedRecv :: Backend -> IO (Int -> IO ByteString)
- newtype SendGather = SendGather (MVar Gathered)
- data Gathered
- = NotGathering
- | Gathering ![[ByteString]] !Int
- gatheredSend :: SendGather -> Backend -> ByteString -> IO ()
- sendBytes :: Connection -> [ByteString] -> IO ()
- sendFramed :: Connection -> Session -> TdsContext -> PacketType -> PacketStatus -> ByteString -> IO ()
- sendPackets :: Connection -> Session -> TdsContext -> PacketType -> (PacketWriter -> IO a) -> IO (Either SomeException a)
- readPacket :: Connection -> IO (Header, ByteString)
- sendMessage :: Connection -> PacketType -> Put -> IO ()
- runBody :: Put -> ByteString
- startRequest :: Connection -> PacketType -> (TdsContext -> ByteString) -> IO ActiveResponse
- sourceAction :: Connection -> IO a -> IO a
- bytesBody :: (TdsContext -> ByteString) -> TdsContext -> PacketWriter -> IO ()
- startRequestFor :: Connection -> Maybe Text -> PacketType -> (TdsContext -> PacketWriter -> IO ()) -> IO ActiveResponse
- startStreamingRequest :: Connection -> Maybe Text -> PacketType -> (PacketWriter -> IO ()) -> IO ActiveResponse
- withReservedSession :: Connection -> IO a -> IO a
- requestReset :: Connection -> IO ()
- resetWithSessionSetup :: Connection -> IO ()
- data RowRead a
- newRowRead :: Int -> CompiledRow a -> IO (RowRead a)
- wholeRows :: RowRead Row
- discardRows :: RowRead ()
- drainingRows :: RowRead ()
- nextStepReversed :: Connection -> ActiveResponse -> RowRead r -> IO (ResponseStep r)
- data RowBatch r = RowBatch {
- batchRows :: ![r]
- batchFailed :: !(Maybe DecodeError)
- data ResponseStep r
- nextStep :: Connection -> ActiveResponse -> RowRead r -> IO (ResponseStep r)
- readPiece :: Connection -> ActiveResponse -> Parser a -> (a -> Maybe (Parser ())) -> IO a
- nextToken :: Connection -> ActiveResponse -> IO (Maybe Token)
- checkCurrent :: Connection -> ActiveResponse -> IO ()
- finishResponse :: Connection -> IO ()
- cancelActiveResponse :: Connection -> ActiveResponse -> IO ()
- readingToEnd :: Connection -> ActiveResponse -> IO a -> IO a
- abandonResponse :: Connection -> ActiveResponse -> IO ()
- drainCancelled :: Connection -> IO ()
- busyStatements :: Connection -> IO [Text]
- drainActiveResponse :: Connection -> ActiveResponse -> IO ()
- readWholeMessage :: Connection -> IO (PacketType, ByteString)
- readWholeMessageUpTo :: Connection -> Int -> IO (PacketType, ByteString)
Documentation
data Connection Source #
An open connection to a server. It belongs to one thread at a time; Hmssql.Connection has the rules.
Constructors
| Connection | |
Fields
| |
How the connection's bytes go to the backend: as they are, or through a TLS session.
Constructors
| TransportPlain !Backend | |
| TransportTls !Context |
What one request at a time travels on: the connection itself, or under
MARS one of its SMP sessions. A session is busy while it has a current
response, that is, until the response's end has been read (StepEnd),
whether by its consumer or by a drain after it was canceled.
Constructors
| Session | |
Fields
| |
data SmpSession Source #
An SMP session's state on the side of the threads that use the connection.
Constructors
| SmpSession | |
Fields
| |
An SMP session's state shared with the reader thread, which refers to
nothing else of the session (see startReader).
Constructors
| SmpLink | |
Fields
| |
What a MARS connection has beyond a plain one.
Constructors
| Mars | |
Fields
| |
data ActiveResponse Source #
The server's reply to one request.
Constructors
| ActiveResponse | |
Fields
| |
data Cancellation Source #
Constructors
| Running | |
| Abandoned | |
| AttentionSent | |
| AttentionAcknowledged |
Instances
| Show Cancellation Source # | |
Defined in Hmssql.Connection.Internal Methods showsPrec :: Int -> Cancellation -> ShowS # show :: Cancellation -> String # showList :: [Cancellation] -> ShowS # | |
| Eq Cancellation Source # | |
Defined in Hmssql.Connection.Internal | |
What has been received but not yet returned: whether the message's last packet has arrived, and where the parse has got to.
Constructors
| ReadState | |
Fields
| |
data ReadPosition Source #
Constructors
| AtToken !ByteString | Between tokens, with the unconsumed payload. |
| Reading (Stream Await IO ReadEvent) | Normalized suspension, ready for a packet or a consumer outcome. |
| Parked !ByteString (Parser ()) | A generic cell pull returned; the recipe drains its remaining row. |
newConnection :: Backend -> TdsContext -> (ServerMessage -> IO ()) -> IO Connection Source #
A connection with no session setup; set connSessionSetup afterwards.
startReader :: Connection -> IO () Source #
Move transport reads onto a dedicated thread. Consumers then block on a
TMVar, which can be interrupted without losing bytes or TLS state. Call
once the transport is final (after login).
When the reader stops, it records why in connEnded: the
ConnectionClosed that recvDirect threw, or ConnectionBroken if it
died of anything else (it is never interrupted except by
closeConnection, which records its own reason first, or by the
finalizer below). It never passes an exception on as data, so a reader
killed by closeConnection doesn't look to the thread waiting for a
response as if that thread itself had been killed.
The thread refers to the transport, connIncoming and connEnded only,
never to the whole Connection: it is always reachable (blocked in a
read), so if it kept the connection reachable, a connection dropped
without close would never be found garbage, and the finalizer below,
which stops the thread and closes the socket, would never run.
Small chunks are copied into shared buffers (copyIntoArena) before
they are passed on.
On a MARS connection (enableMars came first) the thread cuts what it
receives into SMP packets instead and hands each session its TDS
packets (demultiplex); it then refers to the session registry, and
still not to the connection.
enableMars :: Connection -> Int -> IO Connection Source #
Start SMP on a connection that negotiated MARS, once it has logged in
(and run its session setup) in plain TDS: open session 0, with the
given window (at least 4 packets). Before startReader. The
connection returned is the one to use; the one given is no longer.
allSessions :: Connection -> IO [Session] Source #
Every session of the connection: the one, or under MARS the open SMP sessions, in the order of their identifiers.
closeConnection :: Connection -> IO () -> IO () Source #
Mark the connection closed by the client, stop the reader thread, run
goodbye (a TLS close_notify) unless a send is in progress, and close the
socket. A thread waiting for a response or sending, and every later use,
then fails with .ConnectionClosedBy ClosedByClient
A send in progress (or a goodbye, from another closeConnection) may
wait for the server to read, which may be never, so it is not waited for
as it is: the backend is shut down, which makes the send fail at once, and
closed only once the send has given up the send lock. Closing a socket
under a thread that is sending on it would be unsafe: that thread may
read the descriptor just before the close and use it just after, when
the number may already belong to another socket. (Or start to wait on
it after the close: GHC's IO manager (before 9.14) then keeps a stale
registration for the number, which on macOS makes closing the number's
next socket fail; see closeSocket.) The wait for the send lock is
interruptible; the backend is then shut down but not closed, until the
next closeConnection (or the finalizer).
The backend is closed only once the reader thread has stopped: if it were still reading, it could go on to read from whatever socket the next connection is given with the same descriptor.
The connection is kept reachable throughout (keepAlive), as a send
keeps it: past its first lines this refers only to the fields it uses,
so a connection whose last handle was the caller's could otherwise be
found garbage half-way, and the finalizer of startReader would close the
backend under the goodbye. The close_notify was then lost, its send
failing on the closed socket; a send that had read the descriptor just
before the close could have written it to whatever socket was given
the number next.
releaseTransport :: Connection -> IO () Source #
After closeConnection: drop what only a live
transport needs, so that a closed connection that is still referenced (by
a pool, a stream, a thread) keeps little alive. A TLS context keeps its
record buffers and the parameters it was made with, the certificate store
among them: about 3 MB with the system store. The transport becomes the
(closed) backend; nothing uses it any more, since the reader thread has
stopped and sends and reads check connEnded first. A thread that was
sending keeps the TLS context it had until its send fails.
The current response goes too: a response still arriving (a stream read
part-way) keeps the rest of the packet or chunk it stopped in, up to a
whole 32 KiB arena (copyIntoArena). Nothing reads it again, since
nextToken and startRequest check connEnded first; a stream or
thread that still has the response keeps it as long as it does.
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.
connectionContext :: Connection -> IO TdsContext Source #
The protocol state negotiated for the connection: the TDS version, the packet size the server granted, whether a transaction is open, the session's collation.
connectionMars :: Connection -> Bool Source #
Whether the connection negotiated MARS (connectMars):
several responses can be open on it at once, and a new request leaves an
open stream alone. For code that is handed a Connection without the
ConnectInfo it was opened with.
withConnectionLock :: Connection -> IO a -> IO a Source #
Hold the connection's reentrant lock. Refuses (with ReentrantCallback)
a callback's attempt to use the connection: the callback's thread already
holds the lock, so the attempt would otherwise interleave with the response
being read. Refuses a released handle too (checkLease), once the lock
is held, so a handle released while this call waited for the lock is
refused.
checkLease :: Connection -> IO () Source #
Throw ConnectionReleased if this handle is not the connection's
current one: it was released, or renewLease made a newer one.
invalidateLease :: Connection -> IO () Source #
Make every existing handle to the connection stale. The caller holds the lock.
renewLease :: Connection -> IO Connection Source #
A new handle to the connection, making every existing one stale. Its
argument may be stale (that is what it is for), so it takes the mutex
directly, not withConnectionLock: it waits for a request in progress
on another handle rather than cutting in.
renewUsable :: Connection -> IO Connection Source #
renewLease, after throwing what a request would throw if the connection
is unusable. The check comes first, so a refusal leaves the lease alone.
checkNotBroken :: Connection -> IO () Source #
Throws ConnectionBroken after a failure, or after ConnectionClosedBy
ClosedByClientcloseConnection.
refuseUnusable :: Connection -> IO () Source #
Throw the connection's reason if it is broken, closed by the client,
or ended by the server (which marks it broken too), as a request on it
would. For the functions that would otherwise return normally with
nothing to do (cancelActiveStatement, the transaction functions'
checks), so that a normal return says the connection is not known to
be unusable.
currentUnusableReason :: Connection -> IO (Maybe UnusableReason) Source #
Why the connection is known to be unusable, as refuseUnusable would
throw it. It reads what is recorded and leaves it as it is.
markBroken :: Connection -> UnusableReason -> IO () Source #
Record why the connection is unusable. The first reason stays.
reasonFor :: (SomeException -> UnusableReason) -> SomeException -> UnusableReason Source #
The reason an exception gives by its own type, whatever was going on; the function says what to call any other exception.
resetFailure :: SomeException -> UnusableReason Source #
The reason for a failure of the request that carried a reset: the server's refusal, or a request that could not be finished.
throwSevere :: Connection -> Done -> Maybe Text -> IO a Source #
Throw SevereError for a failed DONE. After DONE_SRVERROR the server
closes the connection, so it is broken from here on, as after a severity 20
error, without waiting for the reader thread to see the close.
transportError :: SomeException -> SomeException Source #
The typed form of a synchronous exception from the transport: an
IOException or TLSException becomes ConnectionClosed. TLS
reports the end of the stream (after a TCP EOF without close_notify, as
when the server kills the session) as an error, but it is a close like any
other. Anything else, async exceptions included, is returned unchanged.
recvSome :: Connection -> IO ByteString Source #
Read some bytes. Throws ConnectionClosed on EOF, and after
closeConnection.
recvExactly :: Connection -> Int -> IO ByteString Source #
Read exactly n bytes. Bytes stay in sessRecvBuffer until all n are
available, so an interrupted call loses nothing.
newBufferedRecv :: Backend -> IO (Int -> IO ByteString) Source #
A receive function for a TLS backend: exactly the bytes asked for, or
fewer at the end of the stream (as TLS.getBackend's), copied out of a
32 KiB buffer that is filled by receiving as much as the backend has.
tls reads each record in two calls, its 5-byte header and then its body,
which TLS.getBackend made two system calls; here one receive usually
covers several records. Each call allocates what it returns, as
TLS.getBackend's did; the buffer is the connection's for its life.
One thread reads at a time (the connecting thread during the handshake,
the reader thread after). The buffer is kept alive over each receive
with withForeignPtr, as in recvIntoArena.
newtype SendGather Source #
Where the TLS records of a message go: a TLS backend's send
(gatheredSend) writes each record to the backend at once, unless
sendWith is sending a message, when it gathers them into writes of up
to gatherLimit bytes. Records still hold one TDS packet each
(sendFramed); only the system calls change (a 4 MB parameter took 525
sends of one 8 KB record, which cost the client more CPU than tls's
encryption of it). Every backend send goes through the MVar, so records
tls sends on its own while a message is being gathered (the reader
thread's answer to a KeyUpdate, an alert) are gathered too, in order.
Constructors
| SendGather (MVar Gathered) |
Constructors
| NotGathering | |
| Gathering ![[ByteString]] !Int | Records not yet written, newest first, each a write of one piece
( |
gatheredSend :: SendGather -> Backend -> ByteString -> IO () Source #
A TLS backend's send over the connection's backend: at once, or
gathered while sendWith is sending a message. It refers to the
backend and the gather only, never to the connection (see startReader).
sendBytes :: Connection -> [ByteString] -> IO () Source #
Send a message, as chunks of whole packets (splitIntoPackets,
frameSegments). Returns normally if and only if the whole message was
sent; otherwise the connection is broken.
The chunks are produced as they are sent (sendFramed frames them
lazily), so the caller must have evaluated the message body in full
first: a lazy encoding failure (an error inside a Put) must fail
before anything goes out, not cut a message short. The send runs with asynchronous exceptions masked, so it can only be
interrupted while it actually blocks: waiting for the transport to take
the bytes (a peer that doesn't read), or for the TLS session's write
lock. Any exception that escapes it, asynchronous or not, marks the
connection broken (the server may hold part of a message, and neither it
nor the TLS session can be told to forget it), shuts the socket down so
the reader thread ends and a later closeConnection is clean, and is
rethrown: an interruption reaches the code that sent it, and the
connection then throws ConnectionBroken wrapping it.
A transport failure is thrown as ConnectionClosed (see
transportError), and nothing is sent once the connection has ended: if
the reader thread has seen the server close the connection (as when the
session was killed while idle), or closeConnection was called, that is
the reason given.
sendFramed :: Connection -> Session -> TdsContext -> PacketType -> PacketStatus -> ByteString -> IO () Source #
Frame a message body and send it (sendBytes), in chunks that suit the
transport: on a plain socket, up to 64 KiB of whole packets per write;
over TLS, one packet per chunk, since SQL Server (2025, strict
encryption) resets the connection when a TLS record holds more than one
TDS packet (the records are gathered into writes of up to 64 KiB
instead, SendGather). The body must be evaluated in full already.
On an SMP session each packet goes out as an SMP DATA packet
(sendSmpFrames), and the send lock is held per write, not for the
message; the rest is the same: masked, and any exception out of it
breaks the connection.
sendPackets :: Connection -> Session -> TdsContext -> PacketType -> (PacketWriter -> IO a) -> IO (Either SomeException a) Source #
Send a message whose packets are produced as they are sent: the
producer gets a PacketWriter for a message of this packet type, and the
message is what it writes, ending when it returns. For a message too long to build first (a bulk load), or one produced from an
effectful source.
Like sendBytes, it holds the send lock and runs masked, so the
producer is interrupted only where it blocks (a send that waits for
the transport, or its own source). The packets go out as sendFramed
sends them: up to 64 KiB of whole packets per socket write, or one
TLS record each, gathered.
Returns Right the producer's result once every packet it sent has
been handed to the transport. A synchronous exception from the
producer itself (not from a send) leaves the transport at a packet
boundary, with the message unfinished: it is returned as Left, and
the connection is not broken, for the caller to cancel the message
(startStreamingRequest does). A failed send, or an asynchronous
exception (which may land inside a send), breaks the connection and is
rethrown, as for sendBytes.
readPacket :: Connection -> IO (Header, ByteString) Source #
Read one packet. Interrupted while waiting for it, it loses nothing;
the caller must run it masked if the packet must not be lost once read
(as nextToken does).
This reads the primary session (readSessionPacket): the connection's
one, or SMP session 0.
sendMessage :: Connection -> PacketType -> Put -> IO () Source #
Frame and send a complete message.
runBody :: Put -> ByteString Source #
A message body. binary's runPut starts with a 4 KiB buffer,
which GHC allocates as a large object (taking the storage manager's
lock) for every request, though most bodies are a few dozen bytes; this
starts with 512 bytes and continues in 32 KiB chunks. The chunks aren't
trimmed: the body is copied into packets anyway (frameSegments).
startRequest :: Connection -> PacketType -> (TdsContext -> ByteString) -> IO ActiveResponse Source #
Send a request and start reading its response. Any unfinished previous
response is canceled and drained first; on a MARS connection the
request takes a session of its own instead, and the other responses
stay open (prepareForRequest). A pending requestReset is carried
out first (see there).
sourceAction :: Connection -> IO a -> IO a Source #
User code producing request data may not start a request on this connection.
bytesBody :: (TdsContext -> ByteString) -> TdsContext -> PacketWriter -> IO () Source #
A request body already in memory, as the request writer takes it. The body's length is forced before anything is written, so a lazy encoding failure fails the request before any of it is sent.
startRequestFor :: Connection -> Maybe Text -> PacketType -> (TdsContext -> PacketWriter -> IO ()) -> IO ActiveResponse Source #
startRequest, with the request's SQL text: what names the response
if it is found unfinished (busyStatements).
startStreamingRequest :: Connection -> Maybe Text -> PacketType -> (PacketWriter -> IO ()) -> IO ActiveResponse Source #
startRequest for a message produced packet by packet as it is sent
(sendPackets): the producer gets a PacketWriter, and the message ends
when it returns. A pending requestReset is
left pending (the message isn't a SQL batch or RPC).
If the producer fails (synchronously) with the message unfinished, the
request is canceled: an Attention is sent, and the server's
acknowledgement read (as after an interrupted read, nextToken), so
the connection stays usable; the producer's exception is then
rethrown. A failed or interrupted send breaks the connection, as any
send does.
withReservedSession :: Connection -> IO a -> IO a Source #
Run the action with every request it makes sent on one session, taken
for it; the lock is held throughout. For an exchange that spans
several requests which the server ties to a session: a bulk load's
INSERT BULK, its BULK LOAD message, and the Attention that recovers a
failed load. As without MARS, each request inside first finishes the
session's previous response. Without MARS it is withConnectionLock.
requestReset :: Connection -> IO () Source #
Reset the session before the next SQLBatch, RPC or transaction manager request: the session returns to its state right after login (MS-TDS 2.2.3.1.2, RESETCONNECTION), and the session setup runs again. With no session setup, the next request itself carries the RESETCONNECTION bit, so the reset costs nothing extra; otherwise it is preceded by one batch that carries the bit and the session setup.
Takes the connection lock, so a reset requested by another thread waits
for whatever holds the connection (a request being read, or a whole
withTransaction) and applies to the request after it.
resetWithSessionSetup :: Connection -> IO () Source #
Reset the session and run the session setup again, in one SQLBatch that
carries the RESETCONNECTION bit, read to the end. SET statements inside
an RPC (such as sp_executesql) revert when it returns, and prefixing
them to the caller's batch would break batches that must start with
CREATE PROCEDURE and the like, so this costs a round trip of its own.
If reading the response fails or is interrupted, it is unknown how much
of the setup ran, so the connection is marked broken; likewise if the
setup fails on the server (as in connect, a session
without its setup is not usable): with an ERROR token (SqlServerError),
or with a DONE flagged as failed and no ERROR token (SevereError).
Unlike sp_reset_connection, RESETCONNECTION keeps the transaction
isolation level (SQL Server 2025; Azure SQL Database puts READ COMMITTED
back); nothing here restores it.
What to do with rows, as a response is read.
Constructors
| ReadRows !(Parser a) !(IORef (Maybe (Either ReadFailure a))) | Decode each row: the prepared recipe ( |
| WholeRows (Row -> a) | Keep each row as a |
| DiscardRows a | Skip the rows and keep the other tokens, output parameters included. |
| DrainRows a | Throw the whole response away, the values of output parameters included. |
| StopAtRow | Stop before a row, consuming nothing. A request waiting behind a reset reads only the reset's leading tokens, and reading a row there would mean keeping it for the consumer and parsing it a second time. |
newRowRead :: Int -> CompiledRow a -> IO (RowRead a) Source #
Read rows with this compiled row decoder, given the bound on allocating ahead
of a value's bytes. One RowRead serves a whole result set.
discardRows :: RowRead () Source #
Skip the rows, keeping the other tokens.
drainingRows :: RowRead () Source #
Discard token payloads too, including large RETURNVALUE values.
nextStepReversed :: Connection -> ActiveResponse -> RowRead r -> IO (ResponseStep r) Source #
The rows at the head of the response, decoded: what one nextStep
returns when it finds rows.
Constructors
| RowBatch | |
Fields
| |
data ResponseStep r Source #
What nextStep found next in the response.
nextStep :: Connection -> ActiveResponse -> RowRead r -> IO (ResponseStep r) Source #
Read a batch of rows, a non-row token, or the end of the response. Complete rows are returned before waiting for another packet. A partial token remains as a continuation in the response, without re-reading bytes.
readPiece :: Connection -> ActiveResponse -> Parser a -> (a -> Maybe (Parser ())) -> IO a Source #
Pull a generic cell fragment, preserving a recipe for abandoning its row.
The function says, from what was read, how to skip the rest of the row, or
Nothing when the pull ended at a token boundary.
nextToken :: Connection -> ActiveResponse -> IO (Maybe Token) Source #
Read one raw token. An interrupted receive sends Attention eagerly unless the response has already arrived, and its continuation is retained for the drain. A malformed token breaks the connection.
checkCurrent :: Connection -> ActiveResponse -> IO () Source #
Throw ResultAbandoned unless this response is still its session's
current one: not superseded by a later request (without MARS), canceled
by an interruption or by cancelActiveResponse, drained, or closed
with the connection (which throws its own reason);
ConnectionReleased if the handle was released (checkLease). What nextStep checks before reading; a
stream that holds rows of a batch checks it before yielding each, so
that a superseded stream throws at once, not after the rows it had.
finishResponse :: Connection -> IO () Source #
Make sure no response is pending on the connection: finishSession
for its session, or under MARS for every session that has one. Under
MARS the Attentions all go out first and the acknowledgements are read
after, so settling several running statements costs one round trip, not
one each.
cancelActiveResponse :: Connection -> ActiveResponse -> IO () Source #
Cancel one response and leave the others alone: finishSession for
its session, if the response is still that session's (read part-way, or
canceled by an interrupted read and not yet drained); nothing if it has
ended, was canceled and drained already, or the connection is closed or
broken. A stream of the response throws ResultAbandoned if read on.
readingToEnd :: Connection -> ActiveResponse -> IO a -> IO a Source #
Run an action that reads the response to its end, on a plain or a
MARS connection alike. If it throws part-way, the response is not
left for the next request to find: a synchronous exception cancels and
drains it before it propagates (cancelActiveResponse); an
asynchronous one abandons it (abandonResponse), which cancels the
statement at once and leaves the drain to the next request (or
transaction function, release or cancelActiveStatement), so that an
interruption never waits for the server. (An interruption needn't
land in a read: it can come just after the request was sent, or between
two reads.)
abandonResponse :: Connection -> ActiveResponse -> IO () Source #
After an asynchronous exception out of a function or scope that was
reading the response (readingToEnd, withQueryS): mark the response
abandoned, so that the next request (or transaction function, release
or cancelActiveStatement) drains it; abandon the handle (a new
generation), so that a stream read on through it throws
ResultAbandoned instead of ending at the acknowledgement, silently
missing rows; and cancel the statement at once (cancelStatement).
Nothing if the response has ended or was superseded.
Runs masked (from a handler). It never waits for the statement; it may wait for the connection mutex (taken directly: the handle may be stale, and the response is to be marked all the same) and, on a MARS connection, for a slot in the server's window. The abandoned flag is written before the mutex is taken, so that a second interruption in that wait still leaves the response for the next drain to find (on a superseded response, which nothing consults, the flag is harmless); the other state changes come before the send, since they can't fail.
drainCancelled :: Connection -> IO () Source #
Drain every response that was canceled (an Attention sent, by an
interrupted read) and not yet read to the acknowledgement, or
abandoned by the function that was reading it (readingToEnd). The
caller holds the lock.
busyStatements :: Connection -> IO [Text] Source #
The SQL text of each response on the connection that has not been read to its end or canceled, where known. The caller holds the lock.
drainActiveResponse :: Connection -> ActiveResponse -> IO () Source #
Read and discard the rest of the current response, a batch of rows
at a time (nothing is decoded). The record given is the session's
current one (read from sessResponse, as finishSession does), never
a caller's handle: a handle abandoned by an interruption has an older
generation, which stepResponse would refuse. (Hmssql.BatchQuery
drains with its reader's record on the synchronous path only, where no
generation has changed.)
readWholeMessage :: Connection -> IO (PacketType, ByteString) Source #
Read every packet of one message and return the concatenated body.
readWholeMessageUpTo :: Connection -> Int -> IO (PacketType, ByteString) Source #
readWholeMessage, failing with a ProtocolError once the body is
longer than the given number of bytes, so a server can't make the client
buffer without bound. Used for PRELOGIN, whose response is not a token
stream.