@@ -24,7 +24,7 @@ package io
24
24
package net
25
25
26
26
import com .comcast .ip4s .{IpAddress , SocketAddress }
27
- import cats .effect .Async
27
+ import cats .effect .{ Async , Resource }
28
28
import cats .effect .std .Mutex
29
29
import cats .syntax .all ._
30
30
@@ -33,82 +33,124 @@ import java.nio.channels.{AsynchronousSocketChannel, CompletionHandler}
33
33
import java .nio .{Buffer , ByteBuffer }
34
34
35
35
private [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
+ */
36
40
private [net] def forAsync [F [_]: Async ](
37
41
ch : AsynchronousSocketChannel
38
42
): 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)
41
61
}
62
+ }
42
63
43
64
private [net] abstract class BufferedReads [F [_]](
44
- readMutex : Mutex [F ]
65
+ readMutex : Option [Mutex [F ]],
66
+ writeMutex : Option [Mutex [F ]],
67
+ maxReadChunkSize : Int
45
68
)(implicit F : Async [F ])
46
69
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
+ }
49
75
50
76
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)
59
79
}
60
80
61
81
/** Performs a single channel read operation in to the supplied buffer. */
62
82
protected def readChunk (buffer : ByteBuffer ): F [Int ]
63
83
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 ]
79
89
80
90
def read (max : Int ): F [Option [Chunk [Byte ]]] =
81
91
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
+ }
85
99
}
86
100
}
87
101
88
102
def readN (max : Int ): F [Chunk [Byte ]] =
89
103
withReadBuffer(max) { buffer =>
90
104
def go : F [Chunk [Byte ]] =
91
105
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
95
110
}
96
111
go
97
112
}
98
113
99
114
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
+ (Chunk .byteBuffer(bytes) -> fresh).some.pure
125
+ } else {
126
+ val bytes = buffer.duplicate().asReadOnlyBuffer()
127
+ val slice = buffer.slice()
128
+ (bytes : Buffer ).flip()
129
+ (Chunk .byteBuffer(bytes) -> slice).some.pure
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+ def write (bytes : Chunk [Byte ]): F [Unit ] =
136
+ lock(writeMutex).surround {
137
+ F .delay(bytes.toByteBuffer).flatMap(writeChunk)
138
+ }
101
139
102
- def writes : Pipe [F , Byte , Nothing ] =
103
- _.chunks.foreach(write)
140
+ def writes : Pipe [F , Byte , Nothing ] = { in =>
141
+ Stream .resource(lock(writeMutex)).flatMap { _ =>
142
+ in.chunks.foreach(chunk => writeChunk(chunk.toByteBuffer))
143
+ }
144
+ }
104
145
}
105
146
106
147
private final class AsyncSocket [F [_]](
107
148
ch : AsynchronousSocketChannel ,
108
- readMutex : Mutex [F ],
109
- writeMutex : Mutex [F ]
149
+ readMutex : Option [Mutex [F ]],
150
+ writeMutex : Option [Mutex [F ]],
151
+ maxReadChunkSize : Int
110
152
)(implicit F : Async [F ])
111
- extends BufferedReads [F ](readMutex) {
153
+ extends BufferedReads [F ](readMutex, writeMutex, maxReadChunkSize ) {
112
154
113
155
protected def readChunk (buffer : ByteBuffer ): F [Int ] =
114
156
F .async[Int ] { cb =>
@@ -120,24 +162,18 @@ private[net] trait SocketCompanionPlatform {
120
162
F .delay(Some (endOfInput.voidError))
121
163
}
122
164
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)
165
+ protected def writeChunk (buffer : ByteBuffer ): F [Unit ] =
166
+ F .async[Int ] { cb =>
167
+ ch.write(
168
+ buffer,
169
+ null ,
170
+ new IntCompletionHandler (cb)
171
+ )
172
+ F .delay(Some (endOfOutput.voidError))
173
+ }.flatMap { written =>
174
+ if (written < 0 || buffer.remaining() == 0 ) F .unit
175
+ else writeChunk(buffer)
139
176
}
140
- }
141
177
142
178
def localAddress : F [SocketAddress [IpAddress ]] =
143
179
F .delay(
0 commit comments