/**
* Copyright 2013-2014 Recruit Technologies Co., Ltd. and contributors
* (see CONTRIBUTORS.md)
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. A copy of the
* License is distributed with this work in the LICENSE.md file. You may
* also obtain a copy of the License from
*
* 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 org.gennai.gungnir.tuple.persistent;
import static org.gennai.gungnir.GungnirConfig.*;
import static org.gennai.gungnir.GungnirConst.*;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import org.gennai.gungnir.GungnirConfig;
import org.gennai.gungnir.GungnirManager;
import org.gennai.gungnir.UserEntity;
import org.gennai.gungnir.metastore.MetaStore;
import org.gennai.gungnir.metastore.MetaStoreException;
import org.gennai.gungnir.metastore.NotStoredException;
import org.gennai.gungnir.ql.SchemaRegistry;
import org.gennai.gungnir.tuple.TupleValues;
import org.gennai.gungnir.tuple.schema.Schema;
import org.gennai.gungnir.tuple.schema.TupleSchema;
import org.gennai.gungnir.utils.GungnirUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.codahale.metrics.Timer.Context;
import com.google.common.collect.Lists;
public class PersistentDispatcher {
private static final Logger LOG = LoggerFactory.getLogger(PersistentDispatcher.class);
private UserEntity owner;
private PersistentDeserializer persistentDeserializer;
private PersistentEmitter persistentEmitter;
private GungnirConfig config;
private MetaStore metaStore;
private ReentrantReadWriteLock syncLock;
private SchemaRegistry schemaRegistry;
private LinkedBlockingQueue<TrackingData> deserQueue;
private List<PersistentDeserializer> deserializers;
private ExecutorService deserExecutor;
private LinkedBlockingQueue<TupleValues> emitQueue;
private List<PersistentEmitter> emitters;
private ExecutorService emitExecutor;
private Metrics metrics;
class Metrics {
private MetricRegistry metricRegistry;
private Timer dispatcheTimer;
private Timer deserTimer;
private Timer emitTimer;
private Histogram deserSize;
private Histogram emitSize;
private Meter emitCount;
void prepare() {
metricRegistry = GungnirManager.getManager().getMetricsManager().getRegistry();
metricRegistry.register(METRICS_PERSISTENT_DESER_QUEUE_SIZE + "." + owner.getId(),
new Gauge<Integer>() {
@Override
public Integer getValue() {
return deserQueue.size();
}
});
metricRegistry.register(METRICS_PERSISTENT_EMITTER_QUEUE_SIZE + "." + owner.getId(),
new Gauge<Integer>() {
@Override
public Integer getValue() {
return emitQueue.size();
}
});
dispatcheTimer = metricRegistry.timer(METRICS_PERSISTENT_DISPATCH_TIME + "." + owner.getId());
deserTimer = metricRegistry.timer(METRICS_PERSISTENT_DESER_TIME + "." + owner.getId());
emitTimer = metricRegistry.timer(METRICS_PERSISTENT_EMIT_TIME + "." + owner.getId());
deserSize = metricRegistry.histogram(METRICS_PERSISTENT_DESER_SIZE + "." + owner.getId());
emitSize = metricRegistry.histogram(METRICS_PERSISTENT_EMIT_SIZE + "." + owner.getId());
emitCount = metricRegistry.meter(METRICS_PERSISTENT_EMIT_COUNT + "." + owner.getId());
}
Timer getDispatcheTimer() {
return dispatcheTimer;
}
Timer getDeserTimer() {
return deserTimer;
}
Timer getEmitTimer() {
return emitTimer;
}
Histogram getDeserSize() {
return deserSize;
}
Histogram getEmitSize() {
return emitSize;
}
Meter getEmitCount() {
return emitCount;
}
void cleanup() {
metricRegistry.remove(METRICS_PERSISTENT_DESER_QUEUE_SIZE + "." + owner.getId());
metricRegistry.remove(METRICS_PERSISTENT_EMITTER_QUEUE_SIZE + "." + owner.getId());
metricRegistry.remove(METRICS_PERSISTENT_DISPATCH_TIME + "." + owner.getId());
metricRegistry.remove(METRICS_PERSISTENT_DESER_TIME + "." + owner.getId());
metricRegistry.remove(METRICS_PERSISTENT_EMIT_TIME + "." + owner.getId());
metricRegistry.remove(METRICS_PERSISTENT_EMIT_SIZE + "." + owner.getId());
metricRegistry.remove(METRICS_PERSISTENT_DESER_SIZE + "." + owner.getId());
metricRegistry.remove(METRICS_PERSISTENT_EMIT_COUNT + "." + owner.getId());
}
}
public PersistentDispatcher(UserEntity owner, PersistentDeserializer persistentDeserializer,
PersistentEmitter persistentEmitter) throws MetaStoreException {
this.owner = owner;
this.persistentDeserializer = persistentDeserializer;
this.persistentEmitter = persistentEmitter;
config = GungnirManager.getManager().getConfig();
deserQueue = new LinkedBlockingQueue<TrackingData>(
config.getInteger(PERSISTENT_DESER_QUEUE_SIZE));
emitQueue = new LinkedBlockingQueue<TupleValues>(
config.getInteger(PERSISTENT_EMITTER_QUEUE_SIZE));
metrics = new Metrics();
metrics.prepare();
persistentDeserializer.prepare(this);
persistentEmitter.prepare(this);
metaStore = GungnirManager.getManager().getMetaStore();
syncLock = new ReentrantReadWriteLock();
}
public UserEntity getOwner() {
return owner;
}
public GungnirConfig getConfig() {
return config;
}
public SchemaRegistry getSchemaRegistry() {
return schemaRegistry;
}
LinkedBlockingQueue<TrackingData> getDeserQueue() {
return deserQueue;
}
LinkedBlockingQueue<TupleValues> getEmitQueue() {
return emitQueue;
}
public Metrics getMetrics() {
return metrics;
}
public void sync(SchemaRegistry schemaRegistry) throws MetaStoreException {
WriteLock writeLock = syncLock.writeLock();
writeLock.lock();
try {
if (this.schemaRegistry == null) {
int deserParallelism = config.getInteger(PERSISTENT_DESER_PARALLELISM);
deserExecutor = Executors.newFixedThreadPool(deserParallelism,
GungnirUtils.createThreadFactory("PersistentDeserializer"));
deserializers = Lists.newArrayListWithCapacity(deserParallelism);
for (int i = 0; i < deserParallelism; i++) {
PersistentDeserializer deserializer = persistentDeserializer.clone();
deserializer.prepare(this);
deserializer.sync(schemaRegistry);
deserExecutor.execute(deserializer);
deserializers.add(deserializer);
}
int emitterParallelism = config.getInteger(PERSISTENT_EMITTER_PARALLELISM);
emitExecutor = Executors.newFixedThreadPool(emitterParallelism,
GungnirUtils.createThreadFactory("PersistentEmitter"));
emitters = Lists.newArrayListWithCapacity(emitterParallelism);
for (int i = 0; i < emitterParallelism; i++) {
PersistentEmitter emitter = persistentEmitter.clone();
emitter.prepare(this);
emitter.sync(schemaRegistry);
emitExecutor.execute(emitter);
emitters.add(emitter);
}
this.schemaRegistry = schemaRegistry;
} else {
for (PersistentDeserializer deserializer : deserializers) {
deserializer.pause();
}
for (PersistentDeserializer deserializer : deserializers) {
deserializer.sync(schemaRegistry);
}
for (PersistentEmitter emitter : emitters) {
emitter.sync(schemaRegistry);
}
for (PersistentDeserializer deserializer : deserializers) {
deserializer.resume();
}
this.schemaRegistry = schemaRegistry;
}
} finally {
writeLock.unlock();
}
}
public void dispatch(TrackingData trackingData) throws MetaStoreException, NotStoredException {
Context timerContext = metrics.getDispatcheTimer().time();
ReadLock readLock = syncLock.readLock();
readLock.lock();
try {
Schema schema = schemaRegistry.get(trackingData.getTupleName());
if (schema != null && schema instanceof TupleSchema) {
if (schema.getFieldIndex(TRACKING_ID_FIELD) != null
|| schema.getFieldIndex(TRACKING_NO_FIELD) != null) {
if (trackingData.getTid() == null) {
trackingData.setTid(metaStore.generateTrackingId());
trackingData.setTno(metaStore.getTrackingNo(trackingData.getTid()));
} else {
trackingData.setTno(metaStore.getTrackingNo(trackingData.getTid()));
}
}
deserQueue.put(trackingData);
} else {
LOG.info("{} has not been accepted", trackingData.getTupleName());
}
} catch (InterruptedException e) {
LOG.info("Deserialize queue interrupted");
} finally {
readLock.unlock();
timerContext.stop();
}
}
public void close() {
WriteLock writeLock = syncLock.writeLock();
writeLock.lock();
try {
deserExecutor.shutdownNow();
LOG.info("Deserializer shutdown. account ID: '{}'", owner.getId());
try {
long timeout = deserQueue.size() * 100 + TERMINATION_WAIT_TIME;
if (!deserExecutor.awaitTermination(timeout, TimeUnit.MILLISECONDS)) {
deserExecutor.shutdownNow();
LOG.warn("Deserializer forced shutdown. account ID: '{}'", owner.getId());
}
} catch (InterruptedException e) {
LOG.error("Failed to shutdown deserializer", e);
}
emitExecutor.shutdownNow();
LOG.info("Emitter shutdown. account ID: '{}'", owner.getId());
try {
long timeout = emitQueue.size() * 100 + TERMINATION_WAIT_TIME;
if (!emitExecutor.awaitTermination(timeout, TimeUnit.MILLISECONDS)) {
emitExecutor.shutdownNow();
LOG.warn("Emitter forced shutdown. account ID: '{}'", owner.getId());
}
} catch (InterruptedException e) {
LOG.error("Failed to shutdown emitter", e);
}
} finally {
writeLock.unlock();
}
persistentEmitter.cleanup();
metrics.cleanup();
}
}