KotlinのtoXxx系の型変換用拡張関数とそれによってもたらされるコードの一貫性
どうも、久しぶりの記事です!
なんで書いたの?
Kotlinのコードの一貫性に関する個人的感想を社内開発メンバーに共有したのですが、もったいないからオープンにしようと思い久しぶりに記事を書いてみます。 超短いです。
概要
日々コードを書く中で、Kotlinがコードの一貫性を重視しているなぁとよく感じます。 例えば、toXxx()と呼ばれる多くの変換用の拡張関数が多数あり、多用しています。
例えば、Iterable<T>#toList()
とかIterable<T>#toSet()
とかそういうやつです。
これらの変換用の拡張関数はtoXxxという形式で統一されていることで、ちょっとしたメリットがあります。
メリット
直感的な表現力
toXxx()
という形式は、ある型から別の型への変換を明示的に示すことができます。この明示的な命名と関数の形式は、コードを読んでいる人にとって何を行っているのかが直感的に理解しやすいです。
認知負荷の軽減
toXxx()
一貫した命名規則のおかげで、類推で便利なAPIを呼び出すことができるようになります。
例えば、一度toInt(), toDouble()などの変換関数を知ってしまえば、toRegex()のような関数があるであろうと予想がつき、型を変換するという同様の目的で使えることを容易に想像がつきます。
この一貫性は、Kotlinを使ってプログラムを書く上で非常に価値が高いと感じています。特に、複数人での開発しているアプリコードベースにおいて、この一貫性はコードの品質やメンテナンス性を向上に大いに寄与するでしょう。
そういうプロジェクトではKotlinの初心者が参画してくることも多いと予想できるため、学習コストを下げることにも貢献してくれそうです。
ばいばい
超短いけど、伝えたかったことはこれだけです。
KotlinのtoXxx系の型変換用拡張関数とそれによってもたらされるコードの一貫性
どうも、久しぶりの記事です!
Kotlinのコードの一貫性に関する個人的感想を社内開発メンバーに共有したのですが、もったいないからオープンにしようと思い久しぶりに記事を書いてみます。 超短いです。
日々コードを書く中で、Kotlinがコードの一貫性を重視しているなぁとよく感じます。 例えば、toXxx()と呼ばれる多くの変換用の拡張関数が多数あり、多用しています。
例えば、Iterable<T>#toList()
とかIterable<T>#toSet()
とかそういうやつです。
これらの変換用の拡張関数はtoXxxという形式で統一されていることで、こんなメリットがあります。
- 直感的な表現力
toXxx()
という形式は、ある型から別の型への変換を明示的に示すことができます。この明示的な命名と関数の形式は、コードを読んでいる人にとって何を行っているのかが直感的に理解しやすいです。
- 認知負荷の軽減
toXxx()
一貫した命名規則のおかげで、類推で便利なAPIを呼び出すことができるようになります。例えば、一度toInt(), toDouble()などの変換関数を知ってしまえば、toRegex()のような関数があるであろうと予想がつき、型を変換するという同様の目的で使えることを容易に想像がつきます。この一貫性は、Kotlinを使ってプログラムを書く上で非常に価値が高いと感じています。特に、複数人での開発しているアプリコードベースにおいて、この一貫性はコードの品質やメンテナンス性を向上に大いに寄与するでしょう。そういうプロジェクトではKotlinの初心者が参画してくることも多いと予想できるため、学習コストを下げることにも貢献してくれそうです。
Javaのバージョンは最新に追随するか、LTSを乗り換えていくか
先日こんなことをつぶやいていた
buildpacks使う場合、EOLになったjavaバージョン用のランタイム提供されないのか…
— uuuu.kt (@yushi_koga) April 26, 2021
2週間くらいで消されてる
今の状況で猶予期間が2週間しかないの辛いなぁ
— uuuu.kt (@yushi_koga) April 26, 2021
LTSで動かしておいたほうが良さそうだ
ってことは11に戻して、17まで使い続けねば
2週間じゃないや、1ヶ月ちょっとあるわw
— uuuu.kt (@yushi_koga) April 26, 2021
でも無理だな
サポート切れたの根拠はこれ
— uuuu.kt (@yushi_koga) April 26, 2021
Remove Java 15 🤣https://t.co/OFbpGBOITt
当社では、最新のバージョンでCIだけは回しつつ、実行環境のバージョンをあげるのはLTSのときだけ、くらいが良さそうだなぁ
— uuuu.kt (@yushi_koga) April 26, 2021
どういうことかというと、最新バージョンに追随しようとした場合、buildpacksやもしかしたらその他のFWやライブラリがEOLになったjdkのサポートを直ちにきって、ほんの短い間バージョンを塩漬けにしておきたいと思ってもできない、という状況が起こり得るということ。
少なくともbuildpacksを利用する場合は、LTSを乗り換えていく戦略を取るほうが安全そう。
jdkのリリース後、すぐに乗り換えられるだけの体制が整っていれば別だが、そうじゃないならLTSを乗り換えていきましょう、というお話でした。
Spring Bootのバージョンを2.4.4にあげるときの落とし穴
概要
Spring Bootのバージョンを2.4.4に上げたタイミングで自動的に解決されるmysql-connector-javaのバージョンが8.0.23に変わります。
しかし、このバージョンからタイムゾーンを接続文字列に含めて明示してない(serverTimezoneなどを指定していない)場合に採用されるタイムゾーンが8.0.22以前はサーバ側(MySQLのインスタンスの設定)だったにもかかわらず、8.0.23からは接続元(Spring Bootが動いている環境のタイムゾーン)に変わるので、日時や日付をLocalDateやLocalDateTimeを使っていると9時間ずれて動いてしまいます。
対応策
手っ取り早いのはserverTimezoneまたはconnectionTimeZoneを指定し、MySQLが動いている環境のタイムゾーンと一致させることっぽいです。いやはやハマった…
情報はここ dev.mysql.com
MDCInsertingServletFilterとSentryAppenderを組み合わせると一部のMDCのvalueがnullになっているせいで、処理途中でNPEになってしまいsentryにイベント通知できない
掲題の通りの事象に遭遇して、解決に手間取ったのでメモを残しておきます。
その時ぼやき半分につぶやいていたのはこちら。
spring bootとsentryとlogbackの組み合わせがうまく動かなくてデバッグしている
— uuuu.kt (@yushi_koga) March 27, 2021
少しでもトラブるとライブラリ側のコード読むことになるんだよなぁ
あー、わかった
— uuuu.kt (@yushi_koga) March 27, 2021
MDCでvalueにnullがputされてて、sentryがそれを拾い集めるときにnullかどうかをガード節で検査して、nullだったらNPE投げてる
logbackのMDCInsertingServletFilterを設定していると再現する
んで、nullチェックしてるのはConcurrentHashMapで、nullチェックしていること自体は正しい
— uuuu.kt (@yushi_koga) March 27, 2021
MDCInsertingServletFilterはnullになっている箇所はなかったことを明示するためにも必要な気がする
— uuuu.kt (@yushi_koga) March 27, 2021
となるとSentry側でMDCのvalueがnullの場合の対処方法を考える必要がある気がするなぁ
MDCInsertingServletFilter
からすると、valueにnull
をセットするのは正当な気がしています。
問題が顕在化している箇所はSentryAppender
がMDCの値をコピーする箇所であり、具体的にはCollectionUtils#shallowCopy
を呼び出していてその中でnew ConcurrentHashMap<>(map)
とするときに、ConcurrentHashMap
のコンストラクタ内のガード節でnull
チェックをしており、null
の場合はNPEをthrowしてしまっています。
MDCのvalueがnull
の場合は、空文字に変換すればよいのでは?と一瞬思いましたが、これはデータの改ざん(not加工)であるような気もしており、筋が悪そうです。
何らかの方法でConcurrentHashMap
を辞めれれば良さそうな気がします。1
workaroundですがMDCInsertingServletFilter
をコピペしてCustomMDCInsertingServletFilter
という名前で作り、valueがnull
になりうる箇所はnull
の場合に空文字にするという対応にしました。
package sample import ch.qos.logback.classic.ClassicConstants import org.slf4j.MDC import java.io.IOException import javax.servlet.Filter import javax.servlet.FilterChain import javax.servlet.FilterConfig import javax.servlet.ServletException import javax.servlet.ServletRequest import javax.servlet.ServletResponse import javax.servlet.http.HttpServletRequest // オリジナルはch.qos.logback.classic.helpers.MDCInsertingServletFilter // 値がないときはMDCのvalueにnullをputするが、それだとSentryAppender#createEvent内で、 // CollectionUtils.shallowCopyを使ってMDCの値をコピーしようとしているが、 // ConcurrentHashMapがnullのvalueを受け付けないので、NPEを送出してしまう // OSS側で直してほしい気もするが、正しい挙動がなく直しようがなさそう open class CustomMDCInsertingServletFilter : Filter { override fun destroy() { // do nothing } @Throws(IOException::class, ServletException::class) override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) { insertIntoMDC(request) try { chain.doFilter(request, response) } finally { clearMDC() } } private fun insertIntoMDC(request: ServletRequest) { MDC.put(ClassicConstants.REQUEST_REMOTE_HOST_MDC_KEY, request.remoteHost) if (request is HttpServletRequest) { val httpServletRequest = request MDC.put(ClassicConstants.REQUEST_REQUEST_URI, httpServletRequest.requestURI) val requestURL = httpServletRequest.requestURL if (requestURL != null) { MDC.put(ClassicConstants.REQUEST_REQUEST_URL, requestURL.toString()) } MDC.put(ClassicConstants.REQUEST_METHOD, httpServletRequest.method) MDC.put(ClassicConstants.REQUEST_QUERY_STRING, httpServletRequest.queryString ?: "") // nullになりうるのでnullだった場合は空文字に置換 MDC.put(ClassicConstants.REQUEST_USER_AGENT_MDC_KEY, httpServletRequest.getHeader("User-Agent") ?: "") // nullになりうるのでnullだった場合は空文字に置換 MDC.put(ClassicConstants.REQUEST_X_FORWARDED_FOR, httpServletRequest.getHeader("X-Forwarded-For") ?: "") // nullになりうるのでnullだった場合は空文字に置換 } } private fun clearMDC() { MDC.remove(ClassicConstants.REQUEST_REMOTE_HOST_MDC_KEY) MDC.remove(ClassicConstants.REQUEST_REQUEST_URI) MDC.remove(ClassicConstants.REQUEST_QUERY_STRING) // removing possibly inexistent item is OK MDC.remove(ClassicConstants.REQUEST_REQUEST_URL) MDC.remove(ClassicConstants.REQUEST_METHOD) MDC.remove(ClassicConstants.REQUEST_USER_AGENT_MDC_KEY) MDC.remove(ClassicConstants.REQUEST_X_FORWARDED_FOR) } @Throws(ServletException::class) override fun init(arg0: FilterConfig) { // do nothing } }
これググっても全くHITせず、OSS側のコードをデバッグしないと突き止められませんでした。
MDCInsertingServletFilter
とSentryAppender
を組み合わせて使っている事例は多い気がするので同じように困ってる人の役にたつ情報になってくれれば幸いです。
2021/04/06追記
ちょうどこの記事の投稿の直後ぐらいに問題が報告されて修正が入ったようです。 @wreulickeさん情報提供頂きありがとうございます!
このブログの記事なんですが、もうすぐ直りそうですね
— wreulicke (@wreulicke) April 6, 2021
まだリリースされてなさそうですが。https://t.co/SUKcVCAY9k
Atomic Designの段階の分類の指針について考えていること(2020/6/10バージョン)
概要
掲題についてだらだらと考えたことを表にまとめたのでメモに残しておきます。
大筋としてはそんなに間違えていないと思いますが、兼業フロントエンジニアなのであまり信じすぎないでください…。
前提
nuxt.js
分類とその説明
分類 | 該当フォルダ | 説明 |
---|---|---|
atoms | components/atoms | 原則一つのタグで表現できるもの。 標準のHTMLタグの見た目を装飾したり、 なんらかのアクションのトリガーを追加したものになる傾向が強い。 |
molecules | components/molecules | 2つ以上のタグで構成される意味のあるまとまり。 API呼び出しやvuexの更新といった外部の状態を直接操作するような振る舞いはない。 カルーセルやバリデーション込みの入力フィールドなどが該当する。 |
organisms | components/organisms | 2つ以上のタグで構成される意味のあるまとまり。 フォームやモーダル、テーブルが該当する。 コンポーネントを組合せて共闘動作させるための状態を持つことができる。 外部の状態を操作する振る舞いを持つことができる。 状態や振る舞いを持ってなくてもorganismsとして成立する 作成するコンポーネントをどれに分類するかで一番悩むもののはず。。 |
templates | components/layouts | organisms/molecules/atomsを配置する。 nuxtのlayoutsと混同しないように注意。 乱暴だが、いわゆるプレゼンテーショナルコンポーネントの考え方に近い。 単体で完結したorganismsのみで構成される場合、省略可(になるはず) |
pages | pages | templateに、状態を流し込むのが役割。 これも乱暴だがコンテナコンポーネントの考え方に近い。 単体で完結したorganismsのみで構成される場合、 ただtemplatesを呼び出すだけになるのでその場合はtemplatesを省略可。 |
特徴と段階のマトリクス
項目 | atoms | molecules | oarganisms | templates | pages | 備考 |
---|---|---|---|---|---|---|
振る舞い | ない | ない | ある | ない | ある | 一見、moleclesに見えても振る舞い(ビジネスロジック)があるならorganismsにする |
分割可否 | 不可 | 可 | 可 | 可 | 可 | これ以上分割できないものはatoms。 ただし、他のatomsのベースになりうるatomsはあり得るかもしれない。 例えばボタンコンポーネントに共通して備えさせたいpropsを定義するためのスーパータイプなど |
標準のHTMLタグの数 | 原則1つ | 2つ以上 | 2つ以上 | 2つ以上 | 2つ以上 | |
コンポーネント組合せ数 | n/a | 2つ以上 | 2つ以上 | 2つ以上 | 2つ以上 |
io.lettuce.core.RedisCommandExecutionException: ERR no such key
以下のようなエラーを見かけたので調べてみた。
io.lettuce.core.RedisCommandExecutionException: ERR no such key at io.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:135) at io.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:108) at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:120) at io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:111) at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:59) at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:646) at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:604) at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:556) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965) at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:648) at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:583) at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:500) at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:462) at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.lang.Thread.run(Thread.java:834) org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: ERR no such key at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:54) at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:52) at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41) at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44) at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42) at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:268) at org.springframework.data.redis.connection.lettuce.LettuceKeyCommands.convertLettuceAccessException(LettuceKeyCommands.java:817) at org.springframework.data.redis.connection.lettuce.LettuceKeyCommands.rename(LettuceKeyCommands.java:325) at org.springframework.data.redis.connection.DefaultedRedisConnection.rename(DefaultedRedisConnection.java:118) at org.springframework.data.redis.core.RedisTemplate.lambda$rename$16(RedisTemplate.java:932) at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:224) at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:184) at org.springframework.data.redis.core.RedisTemplate.rename(RedisTemplate.java:931) at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.saveChangeSessionId(RedisOperationsSessionRepository.java:867) at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.saveDelta(RedisOperationsSessionRepository.java:826) at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.access$000(RedisOperationsSessionRepository.java:703) at org.springframework.session.data.redis.RedisOperationsSessionRepository.save(RedisOperationsSessionRepository.java:421) at org.springframework.session.data.redis.RedisOperationsSessionRepository.save(RedisOperationsSessionRepository.java:247) at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.commitSession(SessionRepositoryFilter.java:240) at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.access$100(SessionRepositoryFilter.java:201) at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:154) at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:81) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610) at org.springframework.cloud.sleuth.instrument.web.ExceptionLoggingFilter.doFilter(ExceptionLoggingFilter.java:50) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610) at brave.servlet.TracingFilter.doFilter(TracingFilter.java:65) at org.springframework.cloud.sleuth.instrument.web.LazyTracingFilter.doFilter(TraceWebServletAutoConfiguration.java:145) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610) at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:540) at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:146) at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:566) at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132) at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:257) at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1588) at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:255) at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1345) at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:203) at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:480) at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1557) at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:201) at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1247) at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:144) at org.eclipse.jetty.server.Dispatcher.forward(Dispatcher.java:227) at org.eclipse.jetty.server.Dispatcher.error(Dispatcher.java:86) at org.eclipse.jetty.server.handler.ErrorHandler.doError(ErrorHandler.java:119) at org.eclipse.jetty.server.handler.ErrorHandler.handle(ErrorHandler.java:78) at org.springframework.boot.web.embedded.jetty.JettyEmbeddedErrorHandler.handle(JettyEmbeddedErrorHandler.java:55) at org.eclipse.jetty.server.Response.sendError(Response.java:655) at org.eclipse.jetty.server.handler.AbstractHandler.doError(AbstractHandler.java:100) at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1339) at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:203) at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:480) at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1557) at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:201) at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1247) at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:144) at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132) at org.eclipse.jetty.server.Server.handle(Server.java:502) at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:419) at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260) at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:305) at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103) at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:333) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:310) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:168) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:126) at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:366) at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:765) at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:683) at java.lang.Thread.run(Thread.java:834)
どうも、spring boot 2.1.x系のspring sessionとspring data redisの組み合わせを使っていると出るらしい。 issueも報告されている
以下の通り、spring boot 2.3.x系では直っているので、解決策はバージョンの最新化をすれば良さそう
これ日本語情報が全く無かったので、英語が苦手な人だと困りはてる気がしたのでキーワードだけでもと思いブログに情報を残しておきます。