aboutsummaryrefslogtreecommitdiff
path: root/kamon-core/src/main/scala/kamon/util/LazyActorRef.scala
blob: a07abea678148016153bd4c1b0905c4735e40793 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/*
 * =========================================================================================
 * Copyright © 2013-2015 the kamon project <http://kamon.io/>
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language governing permissions
 * and limitations under the License.
 * =========================================================================================
 */

package kamon.util

import java.util
import java.util.concurrent.ConcurrentLinkedQueue

import akka.actor.{Actor, ActorRef}
import org.HdrHistogram.WriterReaderPhaser

import scala.annotation.tailrec

/**
 *  A LazyActorRef accumulates messages sent to an actor that doesn't exist yet. Once the actor is created and
 *  the LazyActorRef is pointed to it, all the accumulated messages are flushed and any new message sent to the
 *  LazyActorRef will immediately be sent to the pointed ActorRef.
 *
 *  This is intended to be used during Kamon's initialization where some components need to use ActorRefs to work
 *  (like subscriptions and the trace incubator) but our internal ActorSystem is not yet ready to create the
 *  required actors.
 */
class LazyActorRef {
  private val _refPhaser = new WriterReaderPhaser
  private val _backlog = new ConcurrentLinkedQueue[(Any, ActorRef)]()
  @volatile private var _target: Option[ActorRef] = None

  def tell(message: Any)(implicit sender: ActorRef = Actor.noSender): Unit = {
    val criticalEnter = _refPhaser.writerCriticalSectionEnter()
    try {
      _target.map(_.tell(message, sender)) getOrElse {
        _backlog.add((message, sender))
      }

    } finally { _refPhaser.writerCriticalSectionExit(criticalEnter) }
  }

  def point(target: ActorRef): Unit = {
    @tailrec def drain(q: util.Queue[(Any, ActorRef)]): Unit = if (!q.isEmpty) {
      val (msg, sender) = q.poll()
      target.tell(msg, sender)
      drain(q)
    }

    try {
      _refPhaser.readerLock()

      if (_target.isEmpty) {
        _target = Some(target)
        _refPhaser.flipPhase(1000L)
        drain(_backlog)

      } else sys.error("A LazyActorRef cannot be pointed more than once.")
    } finally { _refPhaser.readerUnlock() }
  }
}