blob: 7349a2ab8044a6cbb291fc0611b788545a89a579 [file]
/********************************************************************
* 2014 -
* open source under Apache License Version 2.0
********************************************************************/
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
#include "Atomic.h"
#include "DateTime.h"
#include "Exception.h"
#include "ExceptionInternal.h"
#include "FileSystemInter.h"
#include "HWCrc32c.h"
#include "LeaseRenewer.h"
#include "Logger.h"
#include "OutputStream.h"
#include "OutputStreamImpl.h"
#include "Packet.h"
#include "PacketHeader.h"
#include "SWCrc32c.h"
#include <cassert>
#include <inttypes.h>
namespace Hdfs {
namespace Internal {
OutputStreamImpl::OutputStreamImpl() :
/*heartBeatStop(true),*/ closed(true), isAppend(false), syncBlock(false), checksumSize(0), chunkSize(
0), chunksPerPacket(0), closeTimeout(0), heartBeatInterval(0), packetSize(0), position(
0), replication(0), blockSize(0), bytesWritten(0), cursor(0), lastFlushed(
0), nextSeqNo(0), packets(0), cryptoCodec(NULL), kcp(NULL) {
if (HWCrc32c::available()) {
checksum = shared_ptr < Checksum > (new HWCrc32c());
} else {
checksum = shared_ptr < Checksum > (new SWCrc32c());
}
checksumSize = sizeof(int32_t);
lastSend = steady_clock::now();
#ifdef MOCK
stub = NULL;
#endif
}
OutputStreamImpl::~OutputStreamImpl() {
if (!closed) {
try {
close();
} catch (...) {
}
}
}
void OutputStreamImpl::checkStatus() {
if (closed) {
THROW(HdfsIOException, "OutputStreamImpl: stream is not opened.");
}
lock_guard < mutex > lock(mut);
if (lastError != exception_ptr()) {
rethrow_exception(lastError);
}
}
void OutputStreamImpl::setError(const exception_ptr & error) {
try {
lock_guard < mutex > lock(mut);
lastError = error;
} catch (...) {
}
}
shared_ptr<CryptoCodec> OutputStreamImpl::getCryptoCodec() {
return cryptoCodec;
}
void OutputStreamImpl::setCryptoCodec(shared_ptr<CryptoCodec> cryptoCodec) {
this->cryptoCodec = cryptoCodec;
}
shared_ptr<KmsClientProvider> OutputStreamImpl::getKmsClientProvider() {
return kcp;
}
void OutputStreamImpl::setKmsClientProvider(shared_ptr<KmsClientProvider> kcp) {
this->kcp = kcp;
}
/**
* To create or append a file.
* @param fs hdfs file system.
* @param path the file path.
* @param flag creation flag, can be Create, Append or Create|Overwrite.
* @param permission create a new file with given permission.
* @param createParent if the parent does not exist, create it.
* @param replication create a file with given number of replication.
* @param blockSize create a file with given block size.
*/
void OutputStreamImpl::open(shared_ptr<FileSystemInter> fs, const char * path, int flag,
const Permission & permission, bool createParent, int replication,
int64_t blockSize) {
if (NULL == path || 0 == strlen(path) || replication < 0 || blockSize < 0) {
THROW(InvalidParameter, "Invalid parameter.");
}
if (!(flag == Create || flag == (Create | SyncBlock) || flag == Overwrite
|| flag == (Overwrite | SyncBlock) || flag == Append
|| flag == (Append | SyncBlock) || flag == (Create | Overwrite)
|| flag == (Create | Overwrite | SyncBlock)
|| flag == (Create | Append)
|| flag == (Create | Append | SyncBlock))) {
THROW(InvalidParameter, "Invalid flag.");
}
try {
openInternal(fs, path, flag, permission, createParent, replication,
blockSize);
} catch (...) {
reset();
throw;
}
}
void OutputStreamImpl::computePacketChunkSize() {
int chunkSizeWithChecksum = chunkSize + checksumSize;
static const int packetHeaderSize = PacketHeader::GetPkgHeaderSize();
chunksPerPacket =
(packetSize - packetHeaderSize + chunkSizeWithChecksum - 1)
/ chunkSizeWithChecksum;
chunksPerPacket = chunksPerPacket > 1 ? chunksPerPacket : 1;
packetSize = chunksPerPacket * chunkSizeWithChecksum + packetHeaderSize;
buffer.resize(chunkSize);
}
void OutputStreamImpl::initAppend() {
FileStatus fileInfo;
std::pair<shared_ptr<LocatedBlock>, shared_ptr<FileStatus> > lastBlockWithStatus;
lastBlockWithStatus = filesystem->append(this->path);
lastBlock = lastBlockWithStatus.first;
if (lastBlockWithStatus.second) {
fileInfo = *lastBlockWithStatus.second;
} else {
fileInfo = filesystem->getFileStatus(this->path.c_str());
}
closed = false;
try {
this->blockSize = fileInfo.getBlockSize();
cursor = fileInfo.getLength();
if (lastBlock) {
isAppend = true;
bytesWritten = lastBlock->getNumBytes();
int64_t usedInLastBlock = fileInfo.getLength() % blockSize;
int64_t freeInLastBlock = blockSize - usedInLastBlock;
if (freeInLastBlock == this->blockSize) {
THROW(HdfsIOException,
"OutputStreamImpl: the last block for file %s is full.",
this->path.c_str());
}
int usedInCksum = cursor % chunkSize;
int freeInCksum = chunkSize - usedInCksum;
if (usedInCksum > 0 && freeInCksum > 0) {
/*
* if there is space in the last partial chunk, then
* setup in such a way that the next packet will have only
* one chunk that fills up the partial chunk.
*/
packetSize = 0;
chunkSize = freeInCksum;
} else {
/*
* if the remaining space in the block is smaller than
* that expected size of of a packet, then create
* smaller size packet.
*/
packetSize =
packetSize < freeInLastBlock ?
packetSize : static_cast<int>(freeInLastBlock);
}
}
} catch (...) {
completeFile(false);
reset();
throw;
}
computePacketChunkSize();
}
void OutputStreamImpl::openInternal(shared_ptr<FileSystemInter> fs, const char * path,
int flag, const Permission & permission, bool createParent,
int replication, int64_t blockSize) {
filesystem = fs;
this->path = fs->getStandardPath(path);
this->replication = replication;
this->blockSize = blockSize;
syncBlock = flag & SyncBlock;
conf = shared_ptr < SessionConfig > (new SessionConfig(fs->getConf()));
LOG(DEBUG2, "open file %s for %s", this->path.c_str(), (flag & Append ? "append" : "write"));
packets.setMaxSize(conf->getPacketPoolSize());
if (0 == replication) {
this->replication = conf->getDefaultReplica();
} else {
this->replication = replication;
}
if (0 == blockSize) {
this->blockSize = conf->getDefaultBlockSize();
} else {
this->blockSize = blockSize;
}
chunkSize = conf->getDefaultChunkSize();
packetSize = conf->getDefaultPacketSize();
heartBeatInterval = conf->getHeartBeatInterval();
closeTimeout = conf->getCloseFileTimeout();
if (packetSize < chunkSize) {
THROW(InvalidParameter,
"OutputStreamImpl: packet size %d is less than the chunk size %d.",
packetSize, chunkSize);
}
if (0 != this->blockSize % chunkSize) {
THROW(InvalidParameter,
"OutputStreamImpl: block size %" PRId64 " is not the multiply of chunk size %d.",
this->blockSize, chunkSize);
}
try {
if (flag & Append) {
if (!conf->getKmsUrl().empty()) {
fileStatus = fs->getFileStatus(this->path.c_str());
FileEncryptionInfo *fileEnInfo = fileStatus.getFileEncryption();
if (fileStatus.isFileEncrypted()) {
if (cryptoCodec == NULL) {
auth = shared_ptr<RpcAuth> (
new RpcAuth(fs->getUserInfo(), RpcAuth::ParseMethod(conf->getKmsMethod())));
kcp = shared_ptr<KmsClientProvider> (
new KmsClientProvider(auth, conf));
cryptoCodec = shared_ptr<CryptoCodec> (
new CryptoCodec(fileEnInfo, kcp, conf->getCryptoBufferSize()));
int64_t file_length = fileStatus.getLength();
int ret = cryptoCodec->init(CryptoMethod::ENCRYPT, file_length);
if (ret < 0) {
THROW(HdfsIOException, "init CryptoCodec failed, file:%s", this->path.c_str());
}
}
}
}
initAppend();
LeaseRenewer::GetLeaseRenewer().StartRenew(filesystem);
return;
}
} catch (const FileNotFoundException & e) {
if (!(flag & Create)) {
throw;
}
}
assert((flag & Create) || (flag & Overwrite));
fs->create(this->path, permission, flag, createParent, this->replication,
this->blockSize);
if (!conf->getKmsUrl().empty()) {
fileStatus = fs->getFileStatus(this->path.c_str());
FileEncryptionInfo *fileEnInfo = fileStatus.getFileEncryption();
if (fileStatus.isFileEncrypted()) {
if (cryptoCodec == NULL) {
auth = shared_ptr<RpcAuth>(
new RpcAuth(fs->getUserInfo(), RpcAuth::ParseMethod(conf->getKmsMethod())));
kcp = shared_ptr<KmsClientProvider>(
new KmsClientProvider(auth, conf));
cryptoCodec = shared_ptr<CryptoCodec>(
new CryptoCodec(fileEnInfo, kcp, conf->getCryptoBufferSize()));
int64_t file_length = fileStatus.getLength();
assert(file_length == 0);
int ret = cryptoCodec->init(CryptoMethod::ENCRYPT, file_length);
if (ret < 0) {
THROW(HdfsIOException, "init CryptoCodec failed, file:%s", this->path.c_str());
}
}
}
}
closed = false;
computePacketChunkSize();
LeaseRenewer::GetLeaseRenewer().StartRenew(filesystem);
}
/**
* To append data to file.
* @param buf the data used to append.
* @param size the data size.
*/
void OutputStreamImpl::append(const char * buf, int64_t size) {
LOG(DEBUG3, "append file %s size is %" PRId64 ", offset %" PRId64 " next pos %" PRId64, path.c_str(), size, cursor, size + cursor);
if (NULL == buf || size < 0) {
THROW(InvalidParameter, "Invalid parameter.");
}
checkStatus();
try {
appendInternal(buf, size);
} catch (...) {
setError(current_exception());
throw;
}
}
void OutputStreamImpl::appendInternal(const char * buf, int64_t size) {
int64_t todo = size;
std::string bufEncode;
if (fileStatus.isFileEncrypted()) {
//encrypt buf
bufEncode = cryptoCodec->cipher_wrap(buf, size);
buf = bufEncode.c_str();
}
while (todo > 0) {
int batch = buffer.size() - position;
batch = batch < todo ? batch : static_cast<int>(todo);
/*
* bypass buffer.
*/
if (0 == position && todo >= static_cast<int64_t>(buffer.size())) {
checksum->update(buf + size - todo, batch);
appendChunkToPacket(buf + size - todo, batch);
bytesWritten += batch;
checksum->reset();
} else {
checksum->update(buf + size - todo, batch);
memcpy(&buffer[position], buf + size - todo, batch);
position += batch;
if (position == static_cast<int>(buffer.size())) {
appendChunkToPacket(&buffer[0], buffer.size());
bytesWritten += buffer.size();
checksum->reset();
position = 0;
}
}
todo -= batch;
if (currentPacket
&& (currentPacket->isFull() || bytesWritten == blockSize)) {
sendPacket(currentPacket);
if (isAppend) {
isAppend = false;
chunkSize = conf->getDefaultChunkSize();
packetSize = conf->getDefaultPacketSize();
computePacketChunkSize();
}
if (bytesWritten == blockSize) {
closePipeline();
}
}
}
cursor += size;
}
void OutputStreamImpl::appendChunkToPacket(const char * buf, int size) {
assert(NULL != buf && size > 0);
if (!currentPacket) {
currentPacket = packets.getPacket(packetSize, chunksPerPacket, bytesWritten,
nextSeqNo++, checksumSize);
}
currentPacket->addChecksum(checksum->getValue());
currentPacket->addData(buf, size);
currentPacket->increaseNumChunks();
}
void OutputStreamImpl::sendPacket(shared_ptr<Packet> packet) {
if (!pipeline) {
setupPipeline();
}
pipeline->send(currentPacket);
currentPacket.reset();
lastSend = steady_clock::now();
}
void OutputStreamImpl::setupPipeline() {
assert(currentPacket);
#ifdef MOCK
pipeline = stub->getPipeline();
#else
pipeline = shared_ptr<Pipeline>(new PipelineImpl(isAppend, path.c_str(), *conf, filesystem,
CHECKSUM_TYPE_CRC32C, conf->getDefaultChunkSize(), replication,
currentPacket->getOffsetInBlock(), packets, lastBlock));
#endif
lastSend = steady_clock::now();
/*
* start heart beat beat thread
*/
/*if (heartBeatStop) {
if (heartBeatSender.joinable()) {
heartBeatSender.join();
}
heartBeatStop = false;
heartBeatSender = thread(&OutputStreamImpl::heartBeatSenderRoutine, this);
}*/
}
/**
* Flush all data in buffer and waiting for ack.
* Will block until get all acks.
*/
void OutputStreamImpl::flush() {
LOG(DEBUG3, "flush file %s at offset %" PRId64, path.c_str(), cursor);
checkStatus();
try {
flushInternal(false);
} catch (...) {
setError(current_exception());
throw;
}
}
void OutputStreamImpl::flushInternal(bool needSync) {
if (lastFlushed == cursor && !needSync) {
return;
} else {
lastFlushed = cursor;
}
if (position > 0) {
appendChunkToPacket(&buffer[0], position);
}
/*
* if the pipeline and currentPacket are both NULL,
* that means the pipeline has been closed and no more data in buffer/packet.
* already synced when closing pipeline.
*/
if (!currentPacket && needSync && pipeline) {
currentPacket = packets.getPacket(packetSize, chunksPerPacket, bytesWritten,
nextSeqNo++, checksumSize);
}
lock_guard < mutex > lock(mut);
if (currentPacket) {
currentPacket->setSyncFlag(needSync);
sendPacket(currentPacket);
}
if (pipeline) {
pipeline->flush();
}
}
/**
* return the current file length.
* @return current file length.
*/
int64_t OutputStreamImpl::tell() {
checkStatus();
return cursor;
}
/**
* @ref OutputStream::sync
*/
void OutputStreamImpl::sync() {
LOG(DEBUG3, "sync file %s at offset %" PRId64, path.c_str(), cursor);
checkStatus();
try {
flushInternal(true);
} catch (...) {
setError(current_exception());
throw;
}
}
void OutputStreamImpl::completeFile(bool throwError) {
steady_clock::time_point start = steady_clock::now();
while (true) {
try {
bool success;
success = filesystem->complete(path, lastBlock.get());
if (success) {
return;
}
} catch (HdfsIOException & e) {
if (throwError) {
NESTED_THROW(HdfsIOException,
"OutputStreamImpl: failed to complete file %s.",
path.c_str());
} else {
return;
}
}
if (closeTimeout > 0) {
steady_clock::time_point end = steady_clock::now();
if (ToMilliSeconds(start, end) >= closeTimeout) {
if (throwError) {
THROW(HdfsIOException,
"OutputStreamImpl: timeout when complete file %s, timeout interval %d ms.",
path.c_str(), closeTimeout);
} else {
return;
}
}
}
try {
sleep_for(milliseconds(400));
} catch (...) {
}
}
}
/**
* close the stream.
*/
void OutputStreamImpl::closePipeline() {
lock_guard < mutex > lock(mut);
if (!pipeline) {
return;
}
if (currentPacket) {
sendPacket(currentPacket);
}
currentPacket = packets.getPacket(packetSize, chunksPerPacket, bytesWritten, nextSeqNo++,
checksumSize);
if (syncBlock) {
currentPacket->setSyncFlag(syncBlock);
}
lastBlock = pipeline->close(currentPacket);
assert(lastBlock);
currentPacket.reset();
pipeline.reset();
filesystem->fsync(path);
bytesWritten = 0;
}
void OutputStreamImpl::close() {
exception_ptr e;
if (closed) {
return;
}
try {
//pipeline may be broken
if (!lastError) {
if (lastFlushed != cursor && position > 0) {
appendChunkToPacket(&buffer[0], position);
}
if (lastFlushed != cursor && currentPacket) {
sendPacket(currentPacket);
}
closePipeline();
/*heartBeatStop = true;
condHeartBeatSender.notify_all();
if (heartBeatSender.joinable()) {
heartBeatSender.join();
}*/
completeFile(true);
}
} catch (...) {
e = current_exception();
}
LeaseRenewer::GetLeaseRenewer().StopRenew(filesystem);
LOG(DEBUG3, "close file %s for write with length %" PRId64, path.c_str(), cursor);
reset();
if (e) {
rethrow_exception(e);
}
}
void OutputStreamImpl::reset() {
blockSize = 0;
bytesWritten = 0;
checksum->reset();
chunkSize = 0;
chunksPerPacket = 0;
closed = true;
closeTimeout = 0;
conf.reset();
currentPacket.reset();
cursor = 0;
filesystem.reset();
heartBeatInterval = 0;
isAppend = false;
lastBlock.reset();
lastError = exception_ptr();
lastFlushed = 0;
nextSeqNo = 0;
packetSize = 0;
path.clear();
pipeline.reset();
position = 0;
replication = 0;
syncBlock = false;
}
std::string OutputStreamImpl::toString() {
if (path.empty()) {
return std::string("OutputStream for path ") + path;
} else {
return std::string("OutputStream (not opened)");
}
}
/*void OutputStreamImpl::heartBeatSenderRoutine() {
assert(heartBeatStop == false);
while (!heartBeatStop) {
try {
unique_lock < mutex > lock(mut);
condHeartBeatSender.wait_for(lock, milliseconds(1000));
try {
try {
if (pipeline
&& ToMilliSeconds(lastSend, steady_clock::now())
>= heartBeatInterval) {
pipeline->send(shared_ptr < Packet > (new Packet()));
lastSend = steady_clock::now();
}
} catch (...) {
NESTED_THROW(Hdfs::HdfsIOException, "Failed to send heart beat, path: %s",
path.c_str());
}
} catch (...) {
lastError = current_exception();
throw;
}
} catch (const std::bad_alloc & e) {
* keep quiet if we run out of memory, since writing log need memory,
* that may cause the process terminated.
break;
} catch (const Hdfs::HdfsException & e) {
LOG(LOG_ERROR, "Heart beat thread exit since %s",
GetExceptionDetail(e));
} catch (const std::exception & e) {
LOG(LOG_ERROR, "Heart beat thread exit since %s",
e.what());
}
}
heartBeatStop = true;
}*/
}
}