@@ -24,7 +24,7 @@ package io
2424package net
2525
2626import com .comcast .ip4s .{IpAddress , SocketAddress }
27- import cats .effect .Async
27+ import cats .effect .{ Async , Resource }
2828import cats .effect .std .Mutex
2929import cats .syntax .all ._
3030
@@ -33,82 +33,125 @@ import java.nio.channels.{AsynchronousSocketChannel, CompletionHandler}
3333import java .nio .{Buffer , ByteBuffer }
3434
3535private [net] trait SocketCompanionPlatform {
36+
37+ /** Creates a [[Socket ]] instance for given `AsynchronousSocketChannel`
38+ * with 16 KiB max. read chunk size and exclusive access guards for both reads abd writes.
39+ */
3640 private [net] def forAsync [F [_]: Async ](
3741 ch : AsynchronousSocketChannel
3842 ): F [Socket [F ]] =
39- (Mutex [F ], Mutex [F ]).mapN { (readMutex, writeMutex) =>
40- new AsyncSocket [F ](ch, readMutex, writeMutex)
43+ forAsync(ch, maxReadChunkSize = 16384 , withExclusiveReads = true , withExclusiveWrites = true )
44+
45+ /** Creates a [[Socket ]] instance for given `AsynchronousSocketChannel`.
46+ *
47+ * @param ch async socket channel for actual reads and writes
48+ * @param maxReadChunkSize maximum chunk size for [[Socket#reads ]] method
49+ * @param withExclusiveReads set to `true` if reads should be guarded by mutex
50+ * @param withExclusiveWrites set to `true` if writes should be guarded by mutex
51+ */
52+ private [net] def forAsync [F [_]](
53+ ch : AsynchronousSocketChannel ,
54+ maxReadChunkSize : Int ,
55+ withExclusiveReads : Boolean = false ,
56+ withExclusiveWrites : Boolean = false
57+ )(implicit F : Async [F ]): F [Socket [F ]] = {
58+ def maybeMutex (maybe : Boolean ) = F .defer(if (maybe) Mutex [F ].map(Some (_)) else F .pure(None ))
59+ (maybeMutex(withExclusiveReads), maybeMutex(withExclusiveWrites)).mapN {
60+ (readMutex, writeMutex) => new AsyncSocket [F ](ch, readMutex, writeMutex, maxReadChunkSize)
4161 }
62+ }
4263
4364 private [net] abstract class BufferedReads [F [_]](
44- readMutex : Mutex [F ]
65+ readMutex : Option [Mutex [F ]],
66+ writeMutex : Option [Mutex [F ]],
67+ maxReadChunkSize : Int
4568 )(implicit F : Async [F ])
4669 extends Socket [F ] {
47- private [this ] final val defaultReadSize = 8192
48- private [this ] var readBuffer : ByteBuffer = ByteBuffer .allocate(defaultReadSize)
70+ private def lock (mutex : Option [Mutex [F ]]): Resource [F , Unit ] =
71+ mutex match {
72+ case Some (mutex) => mutex.lock
73+ case None => Resource .unit
74+ }
4975
5076 private def withReadBuffer [A ](size : Int )(f : ByteBuffer => F [A ]): F [A ] =
51- readMutex.lock.surround {
52- F .delay {
53- if (readBuffer.capacity() < size)
54- readBuffer = ByteBuffer .allocate(size)
55- else
56- (readBuffer : Buffer ).limit(size)
57- f(readBuffer)
58- }.flatten
77+ lock(readMutex).surround {
78+ F .delay(ByteBuffer .allocate(size)).flatMap(f)
5979 }
6080
6181 /** Performs a single channel read operation in to the supplied buffer. */
6282 protected def readChunk (buffer : ByteBuffer ): F [Int ]
6383
64- /** Copies the contents of the supplied buffer to a `Chunk[Byte]` and clears the buffer contents. */
65- private def releaseBuffer (buffer : ByteBuffer ): F [Chunk [Byte ]] =
66- F .delay {
67- val read = buffer.position()
68- val result =
69- if (read == 0 ) Chunk .empty
70- else {
71- val dest = new Array [Byte ](read)
72- (buffer : Buffer ).flip()
73- buffer.get(dest)
74- Chunk .array(dest)
75- }
76- (buffer : Buffer ).clear()
77- result
78- }
84+ /** Performs a channel write operation(-s) from the supplied buffer.
85+ *
86+ * Write could be performed multiple times till all buffer remaining contents are written.
87+ */
88+ protected def writeChunk (buffer : ByteBuffer ): F [Unit ]
7989
8090 def read (max : Int ): F [Option [Chunk [Byte ]]] =
8191 withReadBuffer(max) { buffer =>
82- readChunk(buffer).flatMap { read =>
83- if (read < 0 ) F .pure(None )
84- else releaseBuffer(buffer).map(Some (_))
92+ readChunk(buffer).map { read =>
93+ if (read < 0 ) None
94+ else if (buffer.position() == 0 ) Some (Chunk .empty)
95+ else {
96+ (buffer : Buffer ).flip()
97+ Some (Chunk .byteBuffer(buffer.asReadOnlyBuffer()))
98+ }
8599 }
86100 }
87101
88102 def readN (max : Int ): F [Chunk [Byte ]] =
89103 withReadBuffer(max) { buffer =>
90104 def go : F [Chunk [Byte ]] =
91105 readChunk(buffer).flatMap { readBytes =>
92- if (readBytes < 0 || buffer.position() >= max)
93- releaseBuffer(buffer)
94- else go
106+ if (readBytes < 0 || buffer.position() >= max) {
107+ (buffer : Buffer ).flip()
108+ F .pure(Chunk .byteBuffer(buffer.asReadOnlyBuffer()))
109+ } else go
95110 }
96111 go
97112 }
98113
99114 def reads : Stream [F , Byte ] =
100- Stream .repeatEval(read(defaultReadSize)).unNoneTerminate.unchunks
115+ Stream .resource(lock(readMutex)).flatMap { _ =>
116+ Stream .unfoldChunkEval(ByteBuffer .allocate(maxReadChunkSize)) { case buffer =>
117+ readChunk(buffer).flatMap { read =>
118+ if (read < 0 ) none[(Chunk [Byte ], ByteBuffer )].pure
119+ else if (buffer.position() == 0 ) {
120+ (Chunk .empty[Byte ] -> buffer).some.pure
121+ } else if (buffer.remaining() == 0 ) {
122+ val bytes = buffer.asReadOnlyBuffer()
123+ val fresh = ByteBuffer .allocate(maxReadChunkSize)
124+ (bytes : Buffer ).flip()
125+ (Chunk .byteBuffer(bytes) -> fresh).some.pure
126+ } else {
127+ val bytes = buffer.duplicate().asReadOnlyBuffer()
128+ val slice = buffer.slice()
129+ (bytes : Buffer ).flip()
130+ (Chunk .byteBuffer(bytes) -> slice).some.pure
131+ }
132+ }
133+ }
134+ }
135+
136+ def write (bytes : Chunk [Byte ]): F [Unit ] =
137+ lock(writeMutex).surround {
138+ F .delay(bytes.toByteBuffer).flatMap(writeChunk)
139+ }
101140
102- def writes : Pipe [F , Byte , Nothing ] =
103- _.chunks.foreach(write)
141+ def writes : Pipe [F , Byte , Nothing ] = { in =>
142+ Stream .resource(lock(writeMutex)).flatMap { _ =>
143+ in.chunks.foreach(bytes => writeChunk(bytes.toByteBuffer))
144+ }
145+ }
104146 }
105147
106148 private final class AsyncSocket [F [_]](
107149 ch : AsynchronousSocketChannel ,
108- readMutex : Mutex [F ],
109- writeMutex : Mutex [F ]
150+ readMutex : Option [Mutex [F ]],
151+ writeMutex : Option [Mutex [F ]],
152+ maxReadChunkSize : Int
110153 )(implicit F : Async [F ])
111- extends BufferedReads [F ](readMutex) {
154+ extends BufferedReads [F ](readMutex, writeMutex, maxReadChunkSize ) {
112155
113156 protected def readChunk (buffer : ByteBuffer ): F [Int ] =
114157 F .async[Int ] { cb =>
@@ -120,24 +163,18 @@ private[net] trait SocketCompanionPlatform {
120163 F .delay(Some (endOfInput.voidError))
121164 }
122165
123- def write (bytes : Chunk [Byte ]): F [Unit ] = {
124- def go (buff : ByteBuffer ): F [Unit ] =
125- F .async[Int ] { cb =>
126- ch.write(
127- buff,
128- null ,
129- new IntCompletionHandler (cb)
130- )
131- F .delay(Some (endOfOutput.voidError))
132- }.flatMap { written =>
133- if (written >= 0 && buff.remaining() > 0 )
134- go(buff)
135- else F .unit
136- }
137- writeMutex.lock.surround {
138- F .delay(bytes.toByteBuffer).flatMap(go)
166+ protected def writeChunk (buffer : ByteBuffer ): F [Unit ] =
167+ F .async[Int ] { cb =>
168+ ch.write(
169+ buffer,
170+ null ,
171+ new IntCompletionHandler (cb)
172+ )
173+ F .delay(Some (endOfOutput.voidError))
174+ }.flatMap { written =>
175+ if (written < 0 || buffer.remaining() == 0 ) F .unit
176+ else writeChunk(buffer)
139177 }
140- }
141178
142179 def localAddress : F [SocketAddress [IpAddress ]] =
143180 F .delay(
0 commit comments