Skip to content

Commit a502dd1

Browse files
authored
Avoid allocations in TLSEngine when logging is disabled (#2462)
* Avoid allocations in TLSEngine when logging is disabled * Scalafmt * Scalafmt * Fix 2.12 build * Mima exclusions * Scalafmt
1 parent 516f33d commit a502dd1

File tree

9 files changed

+191
-89
lines changed

9 files changed

+191
-89
lines changed

build.sbt

+10-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,16 @@ ThisBuild / mimaBinaryIssueFilters ++= Seq(
9595
ProblemFilters.exclude[MissingClassProblem]("fs2.Pull$CloseScope$"),
9696
ProblemFilters.exclude[ReversedAbstractMethodProblem]("fs2.Pull#CloseScope.*"),
9797
ProblemFilters.exclude[Problem]("fs2.io.Watcher#Registration.*"),
98-
ProblemFilters.exclude[Problem]("fs2.io.Watcher#DefaultWatcher.*")
98+
ProblemFilters.exclude[Problem]("fs2.io.Watcher#DefaultWatcher.*"),
99+
ProblemFilters.exclude[ReversedMissingMethodProblem]("fs2.io.net.tls.TLSContext.clientBuilder"),
100+
ProblemFilters.exclude[ReversedMissingMethodProblem]("fs2.io.net.tls.TLSContext.serverBuilder"),
101+
ProblemFilters.exclude[ReversedMissingMethodProblem](
102+
"fs2.io.net.tls.TLSContext.dtlsClientBuilder"
103+
),
104+
ProblemFilters.exclude[ReversedMissingMethodProblem](
105+
"fs2.io.net.tls.TLSContext.dtlsServerBuilder"
106+
),
107+
ProblemFilters.exclude[Problem]("fs2.io.net.tls.TLSEngine*")
99108
)
100109

101110
lazy val root = project

core/shared/src/test/scala/fs2/StreamCombinatorsSuite.scala

+1-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import scala.concurrent.duration._
2525
import scala.concurrent.TimeoutException
2626
import cats.effect.{IO, SyncIO}
2727
import cats.effect.kernel.Ref
28-
import cats.effect.std.Queue
2928
import cats.effect.std.Semaphore
3029
import cats.syntax.all._
3130
import org.scalacheck.Gen
@@ -1316,7 +1315,7 @@ class StreamCombinatorsSuite extends Fs2Suite {
13161315
val action =
13171316
Vector.fill(streamSize)(Deferred[IO, Unit]).sequence.map { seenArr =>
13181317
def peek(ind: Int)(f: Option[Unit] => Boolean) =
1319-
seenArr.get(ind).fold(true.pure[IO])(_.tryGet.map(f))
1318+
seenArr.get(ind.toLong).fold(true.pure[IO])(_.tryGet.map(f))
13201319

13211320
Stream
13221321
.emits(0 until streamSize)

io/src/main/scala/fs2/io/net/tls/TLSContext.scala

+118-69
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ import javax.net.ssl.{
3333
SSLContext,
3434
SSLEngine,
3535
TrustManagerFactory,
36-
X509ExtendedTrustManager,
37-
X509TrustManager
36+
X509ExtendedTrustManager
3837
}
3938
import cats.Applicative
4039
import cats.effect.kernel.{Async, Resource}
@@ -47,43 +46,85 @@ import java.util.function.BiFunction
4746
*/
4847
sealed trait TLSContext[F[_]] {
4948

50-
/** Creates a `TLSSocket` in client mode, using the supplied parameters.
51-
* Internal debug logging of the session can be enabled by passing a logger.
52-
*/
49+
/** Creates a `TLSSocket` builder in client mode. */
50+
def client(socket: Socket[F]): Resource[F, TLSSocket[F]] =
51+
clientBuilder(socket).build
52+
53+
/** Creates a `TLSSocket` builder in client mode, allowing optional parameters to be configured. */
54+
def clientBuilder(socket: Socket[F]): TLSContext.SocketBuilder[F, TLSSocket]
55+
56+
@deprecated("Use client(socket) or clientBuilder(socket).with(...).build", "3.0.6")
5357
def client(
5458
socket: Socket[F],
5559
params: TLSParameters = TLSParameters.Default,
5660
logger: Option[String => F[Unit]] = None
57-
): Resource[F, TLSSocket[F]]
61+
): Resource[F, TLSSocket[F]] =
62+
clientBuilder(socket).withParameters(params).withOldLogging(logger).build
63+
64+
/** Creates a `TLSSocket` builder in server mode. */
65+
def server(socket: Socket[F]): Resource[F, TLSSocket[F]] =
66+
serverBuilder(socket).build
5867

59-
/** Creates a `TLSSocket` in server mode, using the supplied parameters.
60-
* Internal debug logging of the session can be enabled by passing a logger.
61-
*/
68+
/** Creates a `TLSSocket` builder in server mode, allowing optional parameters to be configured. */
69+
def serverBuilder(socket: Socket[F]): TLSContext.SocketBuilder[F, TLSSocket]
70+
71+
@deprecated("Use server(socket) or serverBuilder(socket).with(...).build", "3.0.6")
6272
def server(
6373
socket: Socket[F],
6474
params: TLSParameters = TLSParameters.Default,
6575
logger: Option[String => F[Unit]] = None
66-
): Resource[F, TLSSocket[F]]
76+
): Resource[F, TLSSocket[F]] =
77+
serverBuilder(socket).withParameters(params).withOldLogging(logger).build
78+
79+
/** Creates a `DTLSSocket` builder in client mode. */
80+
def dtlsClient(
81+
socket: DatagramSocket[F],
82+
remoteAddress: SocketAddress[IpAddress]
83+
): Resource[F, DTLSSocket[F]] =
84+
dtlsClientBuilder(socket, remoteAddress).build
85+
86+
/** Creates a `DTLSSocket` builder in client mode, allowing optional parameters to be configured. */
87+
def dtlsClientBuilder(
88+
socket: DatagramSocket[F],
89+
remoteAddress: SocketAddress[IpAddress]
90+
): TLSContext.SocketBuilder[F, DTLSSocket]
6791

68-
/** Creates a `DTLSSocket` in client mode, using the supplied parameters.
69-
* Internal debug logging of the session can be enabled by passing a logger.
70-
*/
92+
@deprecated(
93+
"Use dtlsClient(socket, remoteAddress) or dtlsClientBuilder(socket, remoteAddress).with(...).build",
94+
"3.0.6"
95+
)
7196
def dtlsClient(
7297
socket: DatagramSocket[F],
7398
remoteAddress: SocketAddress[IpAddress],
7499
params: TLSParameters = TLSParameters.Default,
75100
logger: Option[String => F[Unit]] = None
76-
): Resource[F, DTLSSocket[F]]
101+
): Resource[F, DTLSSocket[F]] =
102+
dtlsClientBuilder(socket, remoteAddress).withParameters(params).withOldLogging(logger).build
77103

78-
/** Creates a `DTLSSocket` in server mode, using the supplied parameters.
79-
* Internal debug logging of the session can be enabled by passing a logger.
80-
*/
104+
/** Creates a `DTLSSocket` builder in server mode. */
105+
def dtlsServer(
106+
socket: DatagramSocket[F],
107+
remoteAddress: SocketAddress[IpAddress]
108+
): Resource[F, DTLSSocket[F]] =
109+
dtlsServerBuilder(socket, remoteAddress).build
110+
111+
/** Creates a `DTLSSocket` builder in client mode, allowing optional parameters to be configured. */
112+
def dtlsServerBuilder(
113+
socket: DatagramSocket[F],
114+
remoteAddress: SocketAddress[IpAddress]
115+
): TLSContext.SocketBuilder[F, DTLSSocket]
116+
117+
@deprecated(
118+
"Use dtlsServer(socket, remoteAddress) or dtlsClientBuilder(socket, remoteAddress).with(...).build",
119+
"3.0.6"
120+
)
81121
def dtlsServer(
82122
socket: DatagramSocket[F],
83123
remoteAddress: SocketAddress[IpAddress],
84124
params: TLSParameters = TLSParameters.Default,
85125
logger: Option[String => F[Unit]] = None
86-
): Resource[F, DTLSSocket[F]]
126+
): Resource[F, DTLSSocket[F]] =
127+
dtlsServerBuilder(socket, remoteAddress).withParameters(params).withOldLogging(logger).build
87128
}
88129

89130
object TLSContext {
@@ -128,35 +169,17 @@ object TLSContext {
128169
ctx: SSLContext
129170
): TLSContext[F] =
130171
new TLSContext[F] {
131-
def client(
132-
socket: Socket[F],
133-
params: TLSParameters,
134-
logger: Option[String => F[Unit]]
135-
): Resource[F, TLSSocket[F]] =
136-
mkSocket(
137-
socket,
138-
true,
139-
params,
140-
logger
141-
)
142-
143-
def server(
144-
socket: Socket[F],
145-
params: TLSParameters,
146-
logger: Option[String => F[Unit]]
147-
): Resource[F, TLSSocket[F]] =
148-
mkSocket(
149-
socket,
150-
false,
151-
params,
152-
logger
153-
)
172+
def clientBuilder(socket: Socket[F]) =
173+
SocketBuilder((p, l) => mkSocket(socket, true, p, l))
174+
175+
def serverBuilder(socket: Socket[F]) =
176+
SocketBuilder((p, l) => mkSocket(socket, false, p, l))
154177

155178
private def mkSocket(
156179
socket: Socket[F],
157180
clientMode: Boolean,
158181
params: TLSParameters,
159-
logger: Option[String => F[Unit]]
182+
logger: TLSLogger[F]
160183
): Resource[F, TLSSocket[F]] =
161184
Resource
162185
.eval(
@@ -174,40 +197,24 @@ object TLSContext {
174197
)
175198
.flatMap(engine => TLSSocket(socket, engine))
176199

177-
def dtlsClient(
200+
def dtlsClientBuilder(
178201
socket: DatagramSocket[F],
179-
remoteAddress: SocketAddress[IpAddress],
180-
params: TLSParameters,
181-
logger: Option[String => F[Unit]]
182-
): Resource[F, DTLSSocket[F]] =
183-
mkDtlsSocket(
184-
socket,
185-
remoteAddress,
186-
true,
187-
params,
188-
logger
189-
)
190-
191-
def dtlsServer(
202+
remoteAddress: SocketAddress[IpAddress]
203+
) =
204+
SocketBuilder((p, l) => mkDtlsSocket(socket, remoteAddress, true, p, l))
205+
206+
def dtlsServerBuilder(
192207
socket: DatagramSocket[F],
193-
remoteAddress: SocketAddress[IpAddress],
194-
params: TLSParameters,
195-
logger: Option[String => F[Unit]]
196-
): Resource[F, DTLSSocket[F]] =
197-
mkDtlsSocket(
198-
socket,
199-
remoteAddress,
200-
false,
201-
params,
202-
logger
203-
)
208+
remoteAddress: SocketAddress[IpAddress]
209+
) =
210+
SocketBuilder((p, l) => mkDtlsSocket(socket, remoteAddress, false, p, l))
204211

205212
private def mkDtlsSocket(
206213
socket: DatagramSocket[F],
207214
remoteAddress: SocketAddress[IpAddress],
208215
clientMode: Boolean,
209216
params: TLSParameters,
210-
logger: Option[String => F[Unit]]
217+
logger: TLSLogger[F]
211218
): Resource[F, DTLSSocket[F]] =
212219
Resource
213220
.eval(
@@ -230,7 +237,7 @@ object TLSContext {
230237
binding: TLSEngine.Binding[F],
231238
clientMode: Boolean,
232239
params: TLSParameters,
233-
logger: Option[String => F[Unit]]
240+
logger: TLSLogger[F]
234241
): F[TLSEngine[F]] = {
235242
val sslEngine = Async[F].blocking {
236243
val engine = ctx.createSSLEngine()
@@ -345,4 +352,46 @@ object TLSContext {
345352
.map(fromSSLContext(_))
346353
}
347354
}
355+
356+
sealed trait SocketBuilder[F[_], S[_[_]]] {
357+
def withParameters(params: TLSParameters): SocketBuilder[F, S]
358+
def withLogging(log: (=> String) => F[Unit]): SocketBuilder[F, S]
359+
def withoutLogging: SocketBuilder[F, S]
360+
def withLogger(logger: TLSLogger[F]): SocketBuilder[F, S]
361+
private[TLSContext] def withOldLogging(log: Option[String => F[Unit]]): SocketBuilder[F, S]
362+
def build: Resource[F, S[F]]
363+
}
364+
365+
object SocketBuilder {
366+
private[TLSContext] type Build[F[_], S[_[_]]] =
367+
(TLSParameters, TLSLogger[F]) => Resource[F, S[F]]
368+
369+
private[TLSContext] def apply[F[_], S[_[_]]](
370+
mkSocket: Build[F, S]
371+
): SocketBuilder[F, S] =
372+
instance(mkSocket, TLSParameters.Default, TLSLogger.Disabled)
373+
374+
private def instance[F[_], S[_[_]]](
375+
mkSocket: Build[F, S],
376+
params: TLSParameters,
377+
logger: TLSLogger[F]
378+
): SocketBuilder[F, S] =
379+
new SocketBuilder[F, S] {
380+
def withParameters(params: TLSParameters): SocketBuilder[F, S] =
381+
instance(mkSocket, params, logger)
382+
def withLogging(log: (=> String) => F[Unit]): SocketBuilder[F, S] =
383+
withLogger(TLSLogger.Enabled(log))
384+
def withoutLogging: SocketBuilder[F, S] =
385+
withLogger(TLSLogger.Disabled)
386+
def withLogger(logger: TLSLogger[F]): SocketBuilder[F, S] =
387+
instance(mkSocket, params, logger)
388+
private[TLSContext] def withOldLogging(
389+
log: Option[String => F[Unit]]
390+
): SocketBuilder[F, S] =
391+
log.map(f => withLogging(m => f(m))).getOrElse(withoutLogging)
392+
def build: Resource[F, S[F]] =
393+
mkSocket(params, logger)
394+
}
395+
}
396+
348397
}

io/src/main/scala/fs2/io/net/tls/TLSEngine.scala

+8-3
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ private[tls] object TLSEngine {
5454
def apply[F[_]: Async](
5555
engine: SSLEngine,
5656
binding: Binding[F],
57-
logger: Option[String => F[Unit]] = None
57+
logger: TLSLogger[F]
5858
): F[TLSEngine[F]] =
5959
for {
6060
wrapBuffer <- InputOutputBuffer[F](
@@ -70,8 +70,13 @@ private[tls] object TLSEngine {
7070
handshakeSemaphore <- Semaphore[F](1)
7171
sslEngineTaskRunner = SSLEngineTaskRunner[F](engine)
7272
} yield new TLSEngine[F] {
73-
private def log(msg: String): F[Unit] =
74-
logger.map(_(msg)).getOrElse(Applicative[F].unit)
73+
private val doLog: (() => String) => F[Unit] =
74+
logger match {
75+
case e: TLSLogger.Enabled[_] => msg => e.log(msg())
76+
case TLSLogger.Disabled => _ => Applicative[F].unit
77+
}
78+
79+
private def log(msg: => String): F[Unit] = doLog(() => msg)
7580

7681
def beginHandshake = Sync[F].delay(engine.beginHandshake())
7782
def session = Sync[F].delay(engine.getSession())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright (c) 2013 Functional Streams for Scala
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
5+
* this software and associated documentation files (the "Software"), to deal in
6+
* the Software without restriction, including without limitation the rights to
7+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8+
* the Software, and to permit persons to whom the Software is furnished to do so,
9+
* subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
package fs2.io.net.tls
23+
24+
sealed trait TLSLogger[+F[_]]
25+
26+
object TLSLogger {
27+
case object Disabled extends TLSLogger[Nothing]
28+
case class Enabled[F[_]](log: (=> String) => F[Unit]) extends TLSLogger[F]
29+
}

io/src/test/scala/fs2/io/net/tls/DTLSSocketSuite.scala

+8-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,14 @@ class DTLSSocketSuite extends TLSSuite {
4545
serverAddress <- address(serverSocket)
4646
clientSocket <- Network[IO].openDatagramSocket()
4747
clientAddress <- address(clientSocket)
48-
tlsServerSocket <- tlsContext.dtlsServer(serverSocket, clientAddress, logger = logger)
49-
tlsClientSocket <- tlsContext.dtlsClient(clientSocket, serverAddress, logger = logger)
48+
tlsServerSocket <- tlsContext
49+
.dtlsServerBuilder(serverSocket, clientAddress)
50+
.withLogger(logger)
51+
.build
52+
tlsClientSocket <- tlsContext
53+
.dtlsClientBuilder(clientSocket, serverAddress)
54+
.withLogger(logger)
55+
.build
5056
} yield (tlsServerSocket, tlsClientSocket, serverAddress)
5157

5258
Stream

io/src/test/scala/fs2/io/net/tls/TLSDebugExample.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ object TLSDebug {
3939
host.resolve.flatMap { socketAddress =>
4040
Network[F].client(socketAddress).use { rawSocket =>
4141
tlsContext
42-
.client(
43-
rawSocket,
42+
.clientBuilder(rawSocket)
43+
.withParameters(
4444
TLSParameters(serverNames = Some(List(new SNIHostName(host.host.toString))))
4545
)
46+
.build
4647
.use { tlsSocket =>
4748
tlsSocket.write(Chunk.empty) >>
4849
tlsSocket.session.map { session =>

0 commit comments

Comments
 (0)