まーぽんって誰がつけたの?

iOS→Scala→インフラなおじさん技術メモ

XcodeのSchemeで値を切り替える方法を探る

背景

APIの向き先とかをTargetを増やしてswiwt other flagsで#if DEBUGとか#if STAGINGでやってる。ので環境が増えるたびにtargetが増えていってしまうのでやめたい。

実行時に環境変数的に値を渡せないか

SchemeのPre-actionsで追加する

  • Build、Run、Test、Archiveで共通して書けるのは、Pre-actionsとPost-actionsである。
  • ここにはshが書ける。ここのshの中で、export HOGE=1を書いてプログラム内から取得できるか。
    • #if HOGE -> ダメ
    • NSProcessInfo.processInfo().environment["HOGE"] -> ダメ
    • InfoPlistにcustom keyを追加して、valueに$(HOGE) -> ダメ
    • Build SettingsにUser-Defined Settingとして追加して、valueとして、$(HOGE) -> ダメ

SchemeのEnvironment Variablesを使う

  • NSProcessInfo.processInfo().environment["HOGE"]で取得できた。
  • しかし、Environment VariablesはRunとTestにしか存在しないので、BuildやArchive時に使えない。
  • xcodebuildコマンド経由でなら環境変数を渡せる可能性はある。

xcconfigの切り替え

やっぱり、これでやるしかなさそうだ。

fastlaneでflags渡す

https://github.com/fastlane/fastlane/issues/408

fastlaneでother swift flags渡す方法もあった。社内配布はどうせfastlane使うのでありかもしれない。

Use this in swift: xcargs: "OTHER_SWIFT_FLAGS='$(inherited) -DVARNAME=1'"

Play FrameworkのRequest.remoteAddressの値が突然変わった

現象

あるサービスの仕組みで処理結果を通知してくるために、自サーバーのapiをたたきにくるときがある。この際に、サーバー側では本当にそのサービスからのリクエストかどうかをみるために、あらかじめIPアドレスを設定しておいてvalidateしてるが、あるリリースのバージョンからvalidateに失敗するようになった。 ログを見てみると、該当サービスじゃなくてELBからのIPアドレスになっていた。

原因

  • play frameworkのverが勝手に上がってしまっていた。
  • addSbtPluginで指定しているplayは2.4.3
  • kamon-playが依存しているのがplay2.4.6だった
  • play2.4.3 -> play2.4.4 でx-forwarded-forヘッダの取得まわりで変更が入っていた
  • x-forwarded-forは簡単にspoofingできてしまうので、信頼できるproxyからだった場合にのみremoteAddressにx-forwarded-forヘッダを利用するようになった
  • サーバーではplay.http.forwarded.proxiesは設定していなかったため、remoteAddressがx-forwarded-forの値ではなく、単にhostの値(ELB)が使われるようになった。

対応

  • play.http.forwarded.proxies = [ "0.0.0.0/0", "::/0" ]を追加
  • Spec追加する
  • ただし、playのversionが上がるのは厳しいので、play2.4.6が入らないようにしたい

sh経由で実行するとeオプションつけてても途中で終了しない

Jenkinsのシェルの実行で#!/bin/sh -xeってつけて途中でエラーが起きたらその時点で、 jobをfailureにするっていうのはよくあると思うんですが、 そのなかで、sh hoge.shみたいに別のshを起動すると、 hoge.shの中でエラーがあったときに、eオプションつけててもhoge.shは一番最後まで実行されちゃう。

どういうことかというと、hoge.shはこんな感じだとして

#!/bin/sh -xe

ls x
echo "hello"
  • 直接実行 -> lsで止まる
$  ./hoge.sh
+ ls x
ls: x: No such file or directory
  • sh経由で実行 -> 最後まで実行される
$  sh hoge.sh
ls: x: No such file or directory
hello

やっぱ、sh経由だとxeオプションつけてても途中で終わってくれないみたい。

sbt testが途中で止まってしまうのを調査する

状況

f:id:masato47744:20170422005236p:plain

という状態になってしまって困った。sbtがグローバルで利用するsbtoptsもプロジェクト共通で指定してるやつがあって他の人は問題なさそうとのこと。ただ、OutOfMemoryなのでJVMのステータスみたいのが見たいと思って調べ始めた。

:dancers: JVMのパフォーマンスを見るにはjstat

jstat - Java 仮想マシン統計データ監視ツール というのがある。 使い方はこんな感じ :point_down: jpsでプロセス番号を探して、jstat -gcutil <プロセス番号> <出力の間隔>。 ざっくり、S0とかS1とかは各領域の使用率みたいなのを表してて、GCってついてるやつはガベージコレクションの回数を表してる。

$ jps
66803 sbt-launch.jar
67844 Jps
$ jstat -gcutil 66803 200
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00   6.25   4.31  18.41  84.36  91.64     43    0.051     0    0.000    0.051
  0.00   6.25   6.00  18.41  84.36  91.64     43    0.051     0    0.000    0.051

:flashlight:sbtが途中で止まってるときはどんなことが起きていたか

JVMは、New領域(さらにEden、S0、S1と分かれる)とOld領域っていうのがあるが、Eden、Old領域が100%で張り付いていて、Full GCがずーっと続いている状態だった。最後OOMで終わったときにはFullGCが357回。

  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
・
中略
・
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   273 1134.327 1149.496
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   274 1139.459 1154.628
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   274 1139.459 1154.628
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   274 1139.459 1154.628
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   274 1139.459 1154.628
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   274 1139.459 1154.628
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   275 1140.528 1155.697
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   275 1140.528 1155.697
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   275 1140.528 1155.697
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   275 1140.528 1155.697
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   275 1140.528 1155.697
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   275 1140.528 1155.697
  0.00 100.00 100.00 100.00  97.14  95.17    144   15.169   275 1140.528 1155.697
・
中略
・
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   357 1529.127 1544.296
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   357 1529.127 1544.296
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   357 1529.127 1544.296
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   357 1529.127 1544.296
  0.00  99.99 100.00 100.00  97.14  95.17    144   15.169   357 1529.127 1544.296
  0.00   0.00   0.25  12.98  91.66  89.80    144   15.169   357 1530.162 1545.331
  0.00   0.00   0.25  12.98  91.66  89.80    144   15.169   357 1530.162 1545.331
↑ oomで終了

プロジェクトで推奨していたsbtoptsのNewSizeがよくなかった?

今まで特に問題なかったsbtoptsの設定

-J-Xmx4096m
-J-Xms2048m
-J-Xss1M
-J-XX:MetaspaceSize=512m
-J-XX:MaxMetaspaceSize=1024m
-J-XX:+UseConcMarkSweepGC
-J-XX:+CMSClassUnloadingEnabled
-J-XX:NewSize=1024m
-J-XX:MaxNewSize=3072m
-J-XX:NewRatio=8
-J-Xss2M
-Duser.timezone=GMT

Old領域が100%で詰まっているっていうことで-J-XX:NewSize=1024m-J-XX:MaxNewSize=3072mが怪しい。そもそも、NewRatioを指定しているので、矛盾している?ような状態になっていた。 NewRatio=8ということは、New領域 : Old領域 = 1 : 8ということらしい。 しかし、MaxNewSizeを3072mとしていたので、多分、つまり始めてしまったころには、New領域 : Old領域 = 3072 : (4096 - 3072) = 3 : 1ぐらいに逆転状態になってしまってしまったのではと推測。 FullGCしてもOld領域が減らないのはなんでなのかはよく分かってないけども。

NewRatio=8だけでやってみた

f:id:masato47744:20170422005346p:plain

FullGCも10回で、sbt testも最後までいくようになった。もうちょいパラメータチューニングしてみてもいいというか、ヒープサイズ自体をもっとでかくしてもいいかもとは思うけど、とりあえずいけるようになったからよかった。

まとめ

jstatというコマンドを生まれて初めて打ちました:tada:

scala.PartialFunction condOpt を使おう

同僚に教えて貰った。

scala.PartialFunction condOpt を使おうに書いてあるサンプルを見るとわかる。

ポイントは

  • match式を使っていて
  • そのmatch式の戻り値の型がOption
  • 先頭からmatchさせていったとき、一番最後以外のmatchした場合については、明示的にSomeに包んで返している
  • なので最後は case _ => None というのがくる

つまりモチベーションとしては

  • 毎回自ら明示的にSomeに包むの面倒
  • case _ => Noneという定形コードうざい
PartialFunction.condOpt(code){
  case SimpleLocale(language) => Lang(language, "")
  case CountryLocale(language, country) => Lang(language, country)
}

// これの返り値は`Option[Lang]`になる!

とにかく、match書いてわざわざSomeで包んで、ないときはNoneっていうコードを書いていたらcondOptを思い出してあげてほしい:pray:

ScalaでHTTPクライアントのライブラリは何を使えばいいのかと思ってAkkaを調べる

scalaのhttpクライアント

今までPlayしかやったことないからWSしか知らなくて、Playじゃないやつでhttpクライアント的なライブラリって何を使えばいいんだろうってのがスタート。Comparing Scala's HTTP client libraries - implicitdefとか見てdispatchってのがいいのかなーと思って同僚に質問してみたらakka-httpがよさそうって話になったので、何者なのかよく分からないので理解をしたいと思って調べた。

そもそもAkkaとは?

What is AkkaのGoogle翻訳

What is Akka? — Akka Documentationの冒頭を適当に日本語訳。

我々は正しい、分散、同時、フォールトトレラントでスケーラブルなアプリケーションを作成することはあまりにもハードであると信じています。我々は間違ったツールと抽象化の間違ったレベルを使用しているので、それはですほとんどの時間。アッカは、それを変更するためにここにあります。アクターモデルを用いて、我々は、抽象化のレベルを高め、より多くの詳細については、反応性マニフェストを、スケーラブルな弾力性と応答性の-参照アプリケーションを構築するためのより良いプラットフォームを提供します。フォールトトレランスのために我々は通信業界は、自己治癒すると停止することはありませんシステムアプリケーションを構築するために大きな成功を収めて使用されている "それがクラッシュしましょう」モデルを採用しています。アクターは、透明分布と真にスケーラブルでフォールトトレラントなアプリケーションのための基礎のための抽象化を提供します。

うーん、なんとなく分かったような分からないような・・・

Akka とはググる

www.slideshare.net

はぁー、、これはとっても分かりやすい。そもそもアクターモデルという原理があってそれを実装してるのがAkkaということかな。 今日はここまで

Xcode上でImplicit declaration of function 'notify_cancel' is invalid in C99 でハマった

Pureeをcocoapodsで導入しようとしてたら

Pureeが依存してるYapDatabaseというやつがCocoaLumberjackに依存してて、CocoaLumberjackのところでタイトルのエラーが出た。

Implicit declaration of function 'notify_cancel' is invalid in C99

f:id:masato47744:20170422004819p:plain

notify_cancelメソッドをクリックすると、osのコードっぽいところ/usr/include/notify.hにいきつく。

f:id:masato47744:20170422004848p:plain

Implicit declaration of function '' is invalid in C99というやつでググるとコンパイラのフラグでどうのこうのとか、osのコードっぽいところまできたので、xcode7.3.1とかxcode8とかswiftのバージョンとかを疑ってしまって、CocoaLumberjackをビルド対象から外せないかとか、other_c_flagsとかで無視できるってissueとかにつられて、盛大にはまってしまった。

敵はここにいた・・

そもそもCocoaLumberjack単体でビルドできるのかとか試してたけどなんの問題もなくてどうしようかなーと考えてて、ふと、エラーが出る冒頭で、#include <notify.h>としてるんだけど、そこをクリックすると、なんとFoxのSDKのNotify.hに飛んだ:joy:

f:id:masato47744:20170422004913p:plain

解決策

Podfileに追記

+  # HEADER_SEARCH_PATHSからfoxSdkを削除する
+  def remove_fox_sdk_from_header_search_path(pod)
+    workDir = Dir.pwd
+    xcconfigFilename = "#{workDir}/Pods/Target\ Support\ Files/#{pod}/#{pod}.xcconfig"
+    xcconfig = File.read(xcconfigFilename)
+    newXcconfig = xcconfig.gsub('"${PODS_ROOT}/Headers/Public/foxSdk"', '')
+    File.open(xcconfigFilename, "w") { |file| file << newXcconfig }
+  end
+
+  ['CocoaLumberjack', 'YapDatabase'].each do |proj|
+    remove_fox_sdk_from_header_search_path proj
+  end

cf: http://dev.classmethod.jp/smartphone/iphone/cocoapods-inherited/

役割を明確化すると働きやすいなと感じた

今まで

入社して開発メンバーとして仕事をしてきて、いつのまにかチームリーダー的な役割になった。 スケジュール間に合うかベロシティを計測したり、やることがなくならないようにタスクを検討したり、新卒メンバーの受け入れをやったり、ミーティングの調整・ファシリテーションしたり、外部との窓口やユーザーの問い合わせに対応したり、1日中プルリクレビューしたり、その他のこぼれ落ちていく雑用を拾ったりしてた。

その結果、いろいろ抱え込みすぎて辛くなった。

役割の明確化

このままだと辛いと言ったらすんなり部署を変えてもらった。 チーム運営みたいなところから離れて今は開発のための開発をする部署に異動になった。

今まで嫌々拾っていた雑用やこぼれ球も開発を支援するという役割になったことで、ポジティブに向き合えるようになった。 雑用も一つのタスクとして記録に残していこうとか、どうやったらこの雑用を自動化できるかとか、部全体として他のチームでも同じようなタスクを巻き取れないかとかそういうことを考えながら取り組めているので楽しい。あと、今までやりたいと思っていたインフラまわりの開発が多いので学ぶことが多くて楽しい。

自分は、リーダーとしてみんなを引っ張っていくというより、メンバーが働きやすくなるようにサポートするという方が性にあってる気がするなーと思った。

今後

インフラもアプリもサーバーもフロントもチーム運営もできるスーパー便利屋になっていきたい。

elasticsearchのindicesとindex

indiceとindexってどう違うか分からなくてなぜかindiceはindexの親概念かなと勘違いしてしまっていて、かなり混乱してたけど、indice 意味 で、検索したらそもそも同義語だった…

kibanaで見る場合はアスタリスクとか使ってどうせ複数のindexをまとめて見ることが多いのでindicesって表現されてるのかも。

fluentdのDockerfileの書き方がちょっと変わってた

ubuntuが16.04になってた

これが一番大きな原因。 ubuntu 16.04が2016/4にリリースされてて、今Dockerhubのubuntuのlatestは16.04になってる。ちなみに、コードネームは「Xenial Xerus(ジーニアル ジリス)」で『おもてなしのアラゲジリス』

https://hub.docker.com/_/ubuntu/

f:id:masato47744:20171110022629p:plain

install.shが変更

  • ubuntu 16.04 xenial用のものが出てる
  • httpsになってる

http://docs.fluentd.org/articles/install-by-deb#step-1--install-from-apt-repository

- http://toolbelt.treasuredata.com/sh/install-ubuntu-trusty-td-agent2.sh
+ https://toolbelt.treasuredata.com/sh/install-ubuntu-xenial-td-agent2.sh 

libssl0.9.8じゃダメ libssl1.0.0

こういうエラーが出る。

E: Package 'libssl0.9.8' has no installation candidate
- apt-get libssl0.9.8
+ apt-get libssl1.0.0 

sudoのinstallが必要

You will be prompted for your password by sudo.
100   563  100   563    0     0   1497      0 --:--:-- --:--:-- --:--:--  1501
sh: 5: sudo: not found
sh: 8: sudo: not found
The command '/bin/sh -c curl -L http://toolbelt.treasuredata.com/sh/install-ubuntu-trusty-td-agent2.sh | sh && mkdir -p /var/log/td-agent' returned a non-zero code: 127
make: *** [build] Error 127

insatall.shでsudoやってるけど、ubuntu 16.04には最初からsudoが入ってないらしくそこで怒られる。なので、apt-getでsudoもインストール。

+ apt-get sudo

余談

W: --force-yes is deprecated, use one of the options starting with --allow instead.

apt-getの際のオプションの--force-yesがdepricatedになったらしく、--alow-xxxを使えとwarningが出る。細かく指定してねってことらしい。今回は無視した。

--force-yes
       Force yes; this is a dangerous option that will cause apt to continue
       without prompting if it is doing something potentially harmful. It
       should not be used except in very special situations. Using force-yes
       can potentially destroy your system! Configuration Item:
       APT::Get::force-yes. This is deprecated and replaced by
       --allow-downgrades, --allow-remove-essential,
       --allow-change-held-packages in 1.1.

airbrake-logbackを非同期化することができなかった

背景

Logbackとは

  • Logback は巷で大人気の log4j プロジェクトの後継プロジェクト
  • java製のデファクトっぽいロギングライブラリ

Airbrakeとは

  • 商用のエラートラッキングシステム

Errbitとは

  • AirbrakeのOSSクローン

airbrake-logbackとは

  • logback はロギングイベントを出力する仕事を、アペンダーと呼ばれるコンポーネントに任せています
  • つまり、LogbackのAirbrakeのためのアペンダー -> Logback Appender for Airbrake

非同期にしたいということはどういうことか

  • 例: AsyncAppenderの設定(logback-examples/src/main/java/chapters/appenders/conc/logback-async.xml)
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>myapp.log</file>
    <encoder>
      <pattern>%logger{35} - %msg%n</pattern>
    </encoder>
  </appender>
  <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="FILE" />
  </appender>
  <root level="DEBUG">
    <appender-ref ref="ASYNC" />
  </root>
</configuration>

結果

airbrakeのappender設定

     <appender name="AIRBRAKE" class="net.anthavio.airbrake.AirbrakeLogbackAppender">
         <apiKey>298c68320df62a7c2a7736ff6cca9b4e</apiKey>
         <env>production - ${config.resource}</env>
         <notify>ALL</notify>
         <url>https://errbit/notifier_api/v2/notices</url>
 
         <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
             <evaluator>
                 <expression>
                     event.getMessage().contains("Logger configuration in conf files is deprecated and has no effect.")
                 </expression>
             </evaluator>
             <onMatch>DENY</onMatch>
         </filter>
 
         <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
              <level>WARN</level>
          </filter>
      </appender>

asyncなappenderの設定

もともと設定されてるAIRBRAKEのappenderをasyncで囲うだけ。

<appender name="ASYNC_AIRBRAKE" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="AIRBRAKE"/>
</appender>

試した結果

root=info root=warn threshold filter どうなったか
AIRBRAKE ASYNC_AIRBRAKE WARN warnが2回飛ぶ 🙅
ASYNC_AIRBRAKE - WARN 何も飛ばない 🙅
- ASYNC_AIRBRAKE WARN 何も飛ばない 🙅
AIRBRAKE, ASYNC_AIRBRAKE - WARN warnが2回飛ぶ 🙅
- ASYNC_AIRBRAKE - 何も飛ばない 🙅

まとめ

  • できなかった
  • そんなに困ってる訳ではないのであきらめた

deis上にhubotをデプロイする

hubot作成

FROM node:latest

RUN mkdir /bot && cd /bot
ADD . /bot

WORKDIR /bot

EXPOSE 11000

CMD ["bin/hubot", "--adapter", "slack"]

deisでアプリケーション作成

  • deisのclientをインストール。homebrewだとver1だったので、shで入れ直す。
  • $ deis register <controller>
  • $ deis create

Slack側の準備

HUBOT_SLACK_TOKEN=xxxxxxxx
HUBOT_SLACK_TEAM=hoge
HUBOT_SLACK_BOTNAME=fuga
HUBOT_SLACK_CHANNELMODE=whitelist
HUBOT_SLACK_CHANNELS=piyo

deisにdeploy

  • $ deis keys:add <- 公開鍵登録
  • $ git push deis master
  • $ deis config:push

ping

  • HUBOT_SLACK_CHANNELSHUBOT_SLACK_BOTNAMEをinviteしてping pong

Future.sequenceは1つずつ順番に実行される訳じゃない

sequenceっていう名前からどうしても順序を意識してしまって順番に(直列に)実行されるものだと思っていた。 でもそうじゃなかった。

Future.applyの場合

ためしに、こんな処理を書いてみる。1から10までを1s待って出力するだけのやつ。

def process: Future[Seq[Int]] = Future.sequence {
  for {
    i <- 1 to 10
  } yield {
    Future {
      println(s"start -> ${i.toString} ${System.currentTimeMillis}")
      Thread.sleep(1000)
      println(s"end -> ${i.toString} ${System.currentTimeMillis}")
      i
    }
  }
}

これを実行すると、出力はこうなる。同時に4つの処理が走るみたいだ。並列で同時に実行されているので全ての処理が終わるまでに3秒ぐらいで終わっている。ただ、最終的な結果は、Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)となって順番通りになっている。

scala> Await.result(process, Duration.Inf)
start -> 1 1459695509918
start -> 3 1459695509919
start -> 2 1459695509919
start -> 4 1459695509919
end -> 3 1459695510923
end -> 4 1459695510923
end -> 1 1459695510923
start -> 6 1459695510923
start -> 7 1459695510923
end -> 2 1459695510923
start -> 5 1459695510923
start -> 8 1459695510923
end -> 8 1459695511929
end -> 7 1459695511929
start -> 9 1459695511929
start -> 10 1459695511929
end -> 5 1459695511929
end -> 6 1459695511929
end -> 9 1459695512933
end -> 10 1459695512933
res24: Seq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Future.successfulの場合

こんな風にFuture.successfulで書き直して

def process: Future[Seq[Int]] = Future.sequence {
  for {
    i <- 1 to 10
  } yield {
    Future.successful {
      println(s"start -> ${i.toString} ${System.currentTimeMillis}")
      Thread.sleep(1000)
      println(s"end -> ${i.toString} ${System.currentTimeMillis}")
      i
    }
  }
}

実行すると、順番に実行される。実行時間も10秒ぐらいかかってる。結果もVector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)で同じになってる。

scala> Await.result(process, Duration.Inf)
start -> 1 1459696443179
end -> 1 1459696444180
start -> 2 1459696444180
end -> 2 1459696445180
start -> 3 1459696445180
end -> 3 1459696446180
start -> 4 1459696446181
end -> 4 1459696447181
start -> 5 1459696447182
end -> 5 1459696448186
start -> 6 1459696448186
end -> 6 1459696449187
start -> 7 1459696449187
end -> 7 1459696450188
start -> 8 1459696450188
end -> 8 1459696451188
start -> 9 1459696451188
end -> 9 1459696452188
start -> 10 1459696452188
end -> 10 1459696453191
res25: Seq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Future.successfulで同じことをした場合は、順番に行われる。これは、Future.applyFuture.successfulの処理をとるときに、名前渡しかそうじゃないかが関係ある。(となんとなく思う)

scala/Future.scala at 2.11.x · scala/scala · GitHub

def apply[T](body: =>T)(implicit @deprecatedName('execctx) executor: ExecutionContext): Future[T] = impl.Future(body)
def successful[T](result: T): Future[T] = Promise.successful(result).future