libdali_adaptor_common_la_DEPENDENCIES =
+# List include directories with more platform-specific (slp) before portable root:
libdali_adaptor_common_la_includes = \
-I../../.. \
- -I../../../platform-abstractions/ \
-I../../../platform-abstractions/slp \
-I../../../platform-abstractions/slp/resource-loader \
- -I../../../adaptors/ \
+ -I../../../platform-abstractions/portable \
+ -I../../../platform-abstractions/ \
-I../../../adaptors/tizen \
+ -I../../../adaptors/ \
-I../../../capi/
daliDefaultFontCacheDir = ${dataReadOnlyDir}/glyphcache/
libdali_adaptor_la_DEPENDENCIES =
+# List include directories with more platform-specific (slp) before portable
+# so platform-specific files can override portable versions:
libdali_adaptor_includes = \
-I../../.. \
-I../../../platform-abstractions/ \
-I../../../platform-abstractions/slp \
-I../../../platform-abstractions/slp/resource-loader \
+ -I../../../platform-abstractions/portable \
-I../../../adaptors/ \
-I../../../adaptors/tizen \
-I../../../capi
-I../../../platform-abstractions/ \
-I../../../platform-abstractions/slp \
-I../../../platform-abstractions/slp/resource-loader \
+ -I../../../platform-abstractions/portable \
-I../../../adaptors/ \
-I../../../adaptors/tizen \
-I../../../capi
-I../../../platform-abstractions/ \
-I../../../platform-abstractions/slp \
-I../../../platform-abstractions/slp/resource-loader \
+ -I../../../platform-abstractions/portable \
-I../../../adaptors/ \
-I../../../adaptors/tizen \
-I../../../capi/
-I../../../platform-abstractions/ \
-I../../../platform-abstractions/slp \
-I../../../platform-abstractions/slp/resource-loader \
+ -I../../../platform-abstractions/portable \
-I../../../adaptors/ \
-I../../../adaptors/tizen \
-I../../../adaptors/tizen\common
-I../../../platform-abstractions/ \
-I../../../platform-abstractions/slp \
-I../../../platform-abstractions/slp/resource-loader \
+ -I../../../platform-abstractions/portable \
-I../../../adaptors/ \
-I../../../adaptors/tizen \
-I../../../capi
--- /dev/null
+#ifndef _DALI_INTERNAL_PLATFORM_FILECLOSER_H__
+#define _DALI_INTERNAL_PLATFORM_FILECLOSER_H__
+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ *
+ */
+
+// INTERNAL INCLUDES
+
+// EXTERNAL INCLUDES
+#include <cstdio>
+
+namespace Dali
+{
+namespace Internal
+{
+namespace Platform
+{
+
+/**
+ * Opens files and closes them later even if an exception is thrown.
+ */
+class FileCloser
+{
+public:
+
+ /**
+ * @brief Construct a FileCloser guarding a new FILE* for accessing the path passed in.
+ */
+ FileCloser( const char * const filename, const char * const mode ) :
+ mFile(fopen(filename, mode))
+ {
+ DALI_ASSERT_DEBUG( filename != 0 && "Cant open a null filename." );
+ DALI_ASSERT_DEBUG( mode != 0 && "Null mode is undefined behaviour in spec." );
+
+ if( mFile == 0 )
+ {
+ DALI_LOG_WARNING( "File open failed for: \"%s\" in mode: \"%s\".\n", filename, mode );
+ }
+ }
+
+ /**
+ * @brief Construct a FileCloser guarding a FILE* for reading out of the memory buffer passed in.
+ */
+ FileCloser( void * const buffer, const size_t bufferSize, const char * const mode ) :
+ mFile( fmemopen( buffer, bufferSize, mode ) )
+ {
+ DALI_ASSERT_DEBUG( buffer != 0 && "Cant open file on null buffer." );
+ DALI_ASSERT_DEBUG( bufferSize > 0 && "Pointless to open file on empty buffer." );
+ DALI_ASSERT_DEBUG( mode != 0 && "Null mode is undefined behaviour in spec." );
+
+ if( mFile == 0 )
+ {
+ DALI_LOG_WARNING( "File open failed for memory buffer at location: \"%p\", of size: \"%u\", in mode: \"%s\".\n", static_cast<void*>(buffer), static_cast<unsigned>(bufferSize), mode );
+ }
+ }
+
+ /**
+ * @brief Destroy the FileCloser and clean up its FILE*.
+ */
+ ~FileCloser()
+ {
+ if( mFile != 0 )
+ {
+ const int closeFailed = fclose( mFile );
+
+ if ( closeFailed )
+ {
+ DALI_LOG_WARNING( "File close failed for FILE: \"%p\".\n", static_cast<void*>(mFile) );
+ }
+ mFile = 0;
+ }
+ }
+
+ /**
+ * @return The FILE* guarded by this object.
+ */
+ FILE* GetFile()
+ {
+ return mFile;
+ }
+
+private:
+ FILE* mFile;
+};
+
+} /* namespace Platform */
+} /* namespace Internal */
+} /* namespace Dali */
+
+#endif /* _DALI_INTERNAL_PLATFORM_FILECLOSER_H__ */
#include <libexif/exif-loader.h>
#include <libexif/exif-tag.h>
#include <setjmp.h>
+#include <boost/thread.hpp>
namespace Dali
{
}
unsigned char * const jpegBufferPtr = &jpegBuffer[0];
- AutoJpg autoJpg(tjInitDecompress());
-
- if(autoJpg.GetHandle() == NULL)
- {
- DALI_LOG_ERROR("%s\n", tjGetErrorStr());
- return false;
- }
-
// Pull the compressed JPEG image bytes out of a file and into memory:
if( fread( jpegBufferPtr, 1, jpegBufferSize, fp ) != jpegBufferSize )
{
DALI_LOG_ERROR("Error seeking to start of file\n");
}
+ // Allow early cancellation between the load and the decompress:
+ boost::this_thread::interruption_point(); ///@warning This can throw an exception.
+
+ AutoJpg autoJpg(tjInitDecompress());
+
+ if(autoJpg.GetHandle() == NULL)
+ {
+ DALI_LOG_ERROR("%s\n", tjGetErrorStr());
+ return false;
+ }
+
JPGFORM_CODE transform = JPGFORM_NONE;
if( attributes.GetOrientationCorrection() )
unsigned char * const bitmapPixelBuffer = bitmap.GetPackedPixelsProfile()->ReserveBuffer(Pixel::RGB888, scaledPostXformWidth, scaledPostXformHeight);
+ // Allow early cancellation before decoding:
+ boost::this_thread::interruption_point(); ///@warning This can throw an exception.
+
const int pitch = scaledPreXformWidth * DECODED_PIXEL_SIZE;
if( tjDecompress2( autoJpg.GetHandle(), jpegBufferPtr, jpegBufferSize, bitmapPixelBuffer, scaledPreXformWidth, pitch, scaledPreXformHeight, DECODED_PIXEL_LIBJPEG_TYPE, flags ) == -1 )
{
const unsigned int bufferWidth = GetTextureDimension( scaledPreXformWidth );
const unsigned int bufferHeight = GetTextureDimension( scaledPreXformHeight );
+ if( transform != JPGFORM_NONE )
+ {
+ // Allow early cancellation before shuffling pixels around on the CPU:
+ boost::this_thread::interruption_point(); ///@warning This can throw an exception.
+ }
+
bool result = false;
switch(transform)
{
namespace Dali
{
+const Integration::ResourceId NO_REQUEST = Integration::ResourceId(0) - 1;
+
namespace SlpPlatform
{
ResourceThreadBase::ResourceThreadBase(ResourceLoader& resourceLoader)
-: mResourceLoader(resourceLoader), mPaused(false)
+: mResourceLoader( resourceLoader ), mCurrentRequestId( NO_REQUEST ), mPaused( false )
{
#if defined(DEBUG_ENABLED)
mLogFilter = Debug::Filter::New(Debug::Concise, false, "LOG_RESOURCE_THREAD_BASE");
// Lock while searching and removing from the request queue:
unique_lock<mutex> lock( mMutex );
- for( RequestQueueIter iterator = mQueue.begin();
- iterator != mQueue.end();
- ++iterator )
+ // See if the request is already launched as the current job on the thread:
+ if( mCurrentRequestId == resourceId )
+ {
+ mThread->interrupt();
+ }
+ // Check the pending requests to be cancelled:
+ else
{
- if( ((*iterator).first).GetId() == resourceId )
+ for( RequestQueueIter iterator = mQueue.begin();
+ iterator != mQueue.end();
+ ++iterator )
{
- iterator = mQueue.erase( iterator );
- break;
+ if( ((*iterator).first).GetId() == resourceId )
+ {
+ iterator = mQueue.erase( iterator );
+ break;
+ }
}
}
}
while( !mResourceLoader.IsTerminating() )
{
- WaitForRequests();
+ try
+ {
+ WaitForRequests();
+
+ if ( !mResourceLoader.IsTerminating() )
+ {
+ ProcessNextRequest();
+ }
+ }
- if ( !mResourceLoader.IsTerminating() )
+ catch( boost::thread_interrupted& ex )
{
- ProcessNextRequest();
+ // No problem, thread was just interrupted from the outside to cancel an in-flight request.
+ boost::thread_interrupted* disableUnusedVarWarning = &ex;
+ ex = *disableUnusedVarWarning;
+ }
+
+ // Since we have an exception handler here anyway, lets catch everything to avoid killing the process:
+ catch( std::exception& ex )
+ {
+ const char * const what = ex.what();
+ DALI_LOG_ERROR( "std::exception caught in resource thread. Aborting request with id %u because of std::exception with reason, \"%s\".\n", unsigned(mCurrentRequestId), what ? what : "null" );
+
+ }
+ catch( Dali::DaliException& ex )
+ {
+ // Probably a failed assert-always:
+ DALI_LOG_ERROR( "DaliException caught in resource thread. Aborting request with id %u. Location: \"%s\". Condition: \"%s\".\n", unsigned(mCurrentRequestId), ex.mLocation.c_str(), ex.mCondition.c_str() );
+ }
+ catch( ... )
+ {
+ DALI_LOG_ERROR( "Unknown exception caught in resource thread. Aborting request with id %u.\n", unsigned(mCurrentRequestId) );
}
}
}
void ResourceThreadBase::WaitForRequests()
{
- unique_lock<mutex> lock(mMutex);
+ unique_lock<mutex> lock( mMutex );
+
+ // Clear the previously current request:
+ mCurrentRequestId = NO_REQUEST;
if( mQueue.empty() || mPaused == true )
{
const RequestInfo & front = mQueue.front();
request = new ResourceRequest( front.first );
type = front.second;
+ mCurrentRequestId = front.first.GetId();
mQueue.pop_front();
}
} // unlock the queue
}
break;
}
+
+ // Clear the interruption status for derived classes that don't implement on-the-fly cancellation yet:
+ boost::this_thread::interruption_point(); ///@warning This can throw an exception.
+ // To support cancellation of an in-flight resource, place the above line at key points in derived
+ // resource thread classes and the loading / decoding / saving code that they call.
+ // See resource-thread-image.cpp and loader-jpeg-turbo.cpp for a conservative example of its use.
+ ///@note: The above line will throw an exception so only place it in exception-safe locations.
}
}
boost::condition_variable mCondition; ///< condition variable
boost::mutex mMutex; ///< used to protect mQueue
RequestQueue mQueue; ///< Request queue
- bool mPaused; ///< Whether to process work in mQueue
+ Integration::ResourceId mCurrentRequestId; ///< Request being processed on thread
+ bool mPaused; ///< Whether to process work in mQueue
#if defined(DEBUG_ENABLED)
public:
#include "loader-ktx.h"
#include "loader-wbmp.h"
+#include "file-closer.h"
+
using namespace std;
using namespace Dali::Integration;
using boost::mutex;
DALI_LOG_TRACE_METHOD( mLogFilter );
DALI_LOG_INFO( mLogFilter, Debug::Verbose, "%s(%s)\n", __FUNCTION__, request.GetPath().c_str() );
- bool file_not_found = false;
+ bool fileNotFound = false;
BitmapPtr bitmap = 0;
bool result = false;
- FILE * const fp = fopen( request.GetPath().c_str(), "rb" );
+ Dali::Internal::Platform::FileCloser fileCloser( request.GetPath().c_str(), "rb" );
+ FILE * const fp = fileCloser.GetFile();
+
if( fp != NULL )
{
result = ConvertStreamToBitmap( *request.GetType(), request.GetPath(), fp, bitmap );
+ // Last chance to interrupt a cancelled load before it is reported back to clients
+ // which have already stopped tracking it:
+ boost::this_thread::interruption_point(); ///@warning This can throw an exception.
if( result && bitmap )
{
// Construct LoadedResource and ResourcePointer for image data
else
{
DALI_LOG_WARNING( "Failed to open file to load \"%s\"\n", request.GetPath().c_str() );
- file_not_found = true;
+ fileNotFound = true;
}
if ( !bitmap )
{
- if( file_not_found )
+ if( fileNotFound )
{
FailedResource resource(request.GetId(), FailureFileNotFound );
mResourceLoader.AddFailedLoad(resource);
if( blobBytes != 0 && blobSize > 0U )
{
// Open a file handle on the memory buffer:
- FILE * const fp = fmemopen(blobBytes, blobSize, "rb");
+ Dali::Internal::Platform::FileCloser fileCloser( blobBytes, blobSize, "rb" );
+ FILE * const fp = fileCloser.GetFile();
if ( fp != NULL )
{
bool result = ConvertStreamToBitmap( *request.GetType(), request.GetPath(), fp, bitmap );
const BitmapResourceType& resType = static_cast<const BitmapResourceType&>(resourceType);
ImageAttributes attributes = resType.imageAttributes;
+ // Check for cancellation now we have hit the filesystem, done some allocation, and burned some cycles:
+ boost::this_thread::interruption_point(); ///@warning This can throw an exception.
+
result = function(fp, *bitmap, attributes);
if (!result)
{
DALI_LOG_WARNING("Image Decoder for %s unavailable\n", path.c_str());
}
- fclose(fp); ///! Not exception safe, but an exception on a resource thread will bring the process down anyway.
}
ptr.Reset( bitmap.Get() );