1
1
package net .lag .kestrel
2
2
3
- import java .nio .ByteBuffer
4
- import java .util .concurrent .ConcurrentLinkedQueue
5
3
import com .twitter .conversions .time ._
4
+ import com .twitter .ostrich .stats .Stats
6
5
import com .twitter .util ._
7
6
import java .io .{IOException , FileOutputStream , File }
7
+ import java .nio .ByteBuffer
8
+ import java .util .concurrent .{ConcurrentLinkedQueue , ScheduledExecutorService , ScheduledFuture , TimeUnit }
9
+
10
+ abstract class PeriodicSyncTask (val scheduler : ScheduledExecutorService , initialDelay : Duration , period : Duration )
11
+ extends Runnable {
12
+ @ volatile private [this ] var scheduledFsync : Option [ScheduledFuture [_]] = None
13
+
14
+ def start () {
15
+ synchronized {
16
+ if (scheduledFsync.isEmpty && period > 0 .seconds) {
17
+ val handle = scheduler.scheduleWithFixedDelay(this , initialDelay.inMilliseconds, period.inMilliseconds,
18
+ TimeUnit .MILLISECONDS )
19
+ scheduledFsync = Some (handle)
20
+ }
21
+ }
22
+ }
23
+
24
+ def stop () {
25
+ synchronized { _stop() }
26
+ }
27
+
28
+ def stopIf (f : => Boolean ) {
29
+ synchronized {
30
+ if (f) _stop()
31
+ }
32
+ }
33
+
34
+ private [this ] def _stop () {
35
+ scheduledFsync.foreach { _.cancel(false ) }
36
+ scheduledFsync = None
37
+ }
38
+ }
8
39
9
40
/**
10
41
* Open a file for writing, and fsync it on a schedule. The period may be 0 to force an fsync
11
42
* after every write, or `Duration.MaxValue` to never fsync.
12
43
*/
13
- class PeriodicSyncFile (file : File , timer : Timer , period : Duration ) {
44
+ class PeriodicSyncFile (file : File , scheduler : ScheduledExecutorService , period : Duration ) {
14
45
// pre-completed future for writers who are behaving synchronously.
15
46
private final val DONE = Future (())
16
47
17
- val writer = new FileOutputStream (file, true ).getChannel
18
- val promises = new ConcurrentLinkedQueue [Promise [Unit ]]()
19
-
20
- @ volatile var closed = false
48
+ case class TimestampedPromise (val promise : Promise [Unit ], val time : Time )
21
49
22
- if (period > 0 .seconds && period < Duration .MaxValue ) {
23
- timer.schedule(Time .now, period) {
50
+ val writer = new FileOutputStream (file, true ).getChannel
51
+ val promises = new ConcurrentLinkedQueue [TimestampedPromise ]()
52
+ val periodicSyncTask = new PeriodicSyncTask (scheduler, period, period) {
53
+ override def run () {
24
54
if (! closed && ! promises.isEmpty) fsync()
25
55
}
26
56
}
27
57
58
+ @ volatile var closed = false
59
+
28
60
private def fsync () {
29
61
synchronized {
30
62
// race: we could underestimate the number of completed writes. that's okay.
31
63
val completed = promises.size
64
+ val fsyncStart = Time .now
32
65
try {
33
66
writer.force(false )
34
67
} catch {
35
68
case e : IOException =>
36
69
for (i <- 0 until completed) {
37
- promises.poll().setException(e)
70
+ promises.poll().promise. setException(e)
38
71
}
39
72
return ;
40
73
}
41
74
42
75
for (i <- 0 until completed) {
43
- promises.poll().setValue(())
76
+ val timestampedPromise = promises.poll()
77
+ timestampedPromise.promise.setValue(())
78
+ val delaySinceWrite = fsyncStart - timestampedPromise.time
79
+ val durationBehind = if (delaySinceWrite > period) delaySinceWrite - period else 0 .seconds
80
+ Stats .addMetric(" fsync_delay_usec" , durationBehind.inMicroseconds.toInt)
44
81
}
82
+
83
+ periodicSyncTask.stopIf { promises.isEmpty }
45
84
}
46
85
}
47
86
@@ -62,7 +101,8 @@ class PeriodicSyncFile(file: File, timer: Timer, period: Duration) {
62
101
DONE
63
102
} else {
64
103
val promise = new Promise [Unit ]()
65
- promises.add(promise)
104
+ promises.add(TimestampedPromise (promise, Time .now))
105
+ periodicSyncTask.start()
66
106
promise
67
107
}
68
108
}
@@ -73,6 +113,7 @@ class PeriodicSyncFile(file: File, timer: Timer, period: Duration) {
73
113
*/
74
114
def close () {
75
115
closed = true
116
+ periodicSyncTask.stop()
76
117
fsync()
77
118
writer.close()
78
119
}
0 commit comments