Merge "Fix prevent for resource leak" into devel/master
[platform/core/uifw/dali-adaptor.git] / platform-abstractions / tizen / resource-loader / resource-thread-base.cpp
1 /*
2  * Copyright (c) 2014 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // EXTERNAL INCLUDES
19 #include <memory>
20 #include <dali/integration-api/debug.h>
21
22 // INTERNAL INCLUDES
23 #include "resource-thread-base.h"
24 #include "tizen-logging.h"
25 #include "atomics.h"
26
27 using namespace Dali::Integration;
28
29 namespace Dali
30 {
31
32 // Initial values for the members tracking which resources have been cancelled.
33 // They start out with different values so that if the first load executed is
34 // synchronous, it won't be erroneously cancelled.
35 const Integration::ResourceId NO_REQUEST_IN_FLIGHT = Integration::ResourceId(0) - 1;
36 const Integration::ResourceId NO_REQUEST_CANCELLED = Integration::ResourceId(0) - 2;
37
38 namespace TizenPlatform
39 {
40 using Internal::Adaptor::ConditionalWait;
41
42 namespace
43 {
44 const char * const IDLE_PRIORITY_ENVIRONMENT_VARIABLE_NAME = "DALI_RESOURCE_THREAD_IDLE_PRIORITY";
45 } // unnamed namespace
46
47 /** Thrown by InterruptionPoint() to abort a request early. */
48 class CancelRequestException {};
49
50 ResourceThreadBase::ResourceThreadBase( ResourceLoader& resourceLoader ) :
51   mResourceLoader( resourceLoader ),
52   mThread( 0 ),
53   mCurrentRequestId( NO_REQUEST_IN_FLIGHT ),
54   mCancelRequestId( NO_REQUEST_CANCELLED ),
55   mPaused( false )
56 {
57 #if defined(DEBUG_ENABLED)
58   mLogFilter = Debug::Filter::New(Debug::Concise, false, "LOG_RESOURCE_THREAD_BASE");
59 #endif
60
61   int error = pthread_create( &mThread, NULL, InternalThreadEntryFunc, this );
62   DALI_ASSERT_ALWAYS( !error && "Error in pthread_create()" );
63 }
64
65 ResourceThreadBase::~ResourceThreadBase()
66 {
67   TerminateThread();
68
69 #if defined(DEBUG_ENABLED)
70   delete mLogFilter;
71 #endif
72 }
73
74 void ResourceThreadBase::TerminateThread()
75 {
76   if (mThread)
77   {
78     // wake thread
79     mCondition.Notify();
80
81     // wait for thread to exit
82     pthread_join( mThread, NULL );
83
84     mThread = 0;
85   }
86 }
87
88 void ResourceThreadBase::AddRequest(const ResourceRequest& request, const RequestType type)
89 {
90   bool wasEmpty = false;
91   bool wasPaused = false;
92
93   {
94     // Lock while adding to the request queue
95     ConditionalWait::ScopedLock lock( mCondition );
96
97     wasEmpty = mQueue.empty();
98     wasPaused = mPaused;
99
100     mQueue.push_back( std::make_pair(request, type) );
101   }
102
103   if( wasEmpty && !wasPaused )
104   {
105     // Wake-up the thread
106     mCondition.Notify();
107   }
108 }
109
110 // Called from outer thread.
111 void ResourceThreadBase::CancelRequest( const Integration::ResourceId resourceId )
112 {
113   bool found = false;
114   DALI_LOG_INFO( mLogFilter, Debug::Verbose, "%s: %u.\n", __FUNCTION__, unsigned(resourceId) );
115
116   // Eliminate the cancelled request from the request queue if it is in there:
117   {
118     // Lock while searching and removing from the request queue:
119     ConditionalWait::ScopedLock lock( mCondition );
120
121     for( RequestQueueIter iterator = mQueue.begin();
122          iterator != mQueue.end();
123          ++iterator )
124     {
125       if( ((*iterator).first).GetId() == resourceId )
126       {
127         iterator = mQueue.erase( iterator );
128         found = true;
129         break;
130       }
131     }
132   }
133
134   // Remember the cancelled id for the worker thread to poll at one of its points
135   // of interruption:
136   if( !found )
137   {
138     Dali::Internal::AtomicWriteToCacheableAlignedAddress( &mCancelRequestId, resourceId );
139     DALI_LOG_INFO( mLogFilter, Debug::Concise, "%s: Cancelling in-flight resource (%u).\n", __FUNCTION__, unsigned(resourceId) );
140   }
141 }
142
143 // Called from worker thread.
144 void ResourceThreadBase::InterruptionPoint() const
145 {
146   const Integration::ResourceId cancelled = Dali::Internal::AtomicReadFromCacheableAlignedAddress( &mCancelRequestId );
147   const Integration::ResourceId current = mCurrentRequestId;
148
149   if( current == cancelled )
150   {
151     DALI_LOG_INFO( mLogFilter, Debug::Concise, "%s: Cancelled in-flight resource (%u).\n", __FUNCTION__, unsigned(cancelled) );
152     throw CancelRequestException();
153   }
154 }
155
156 void* ResourceThreadBase::InternalThreadEntryFunc( void* This )
157 {
158     ( static_cast<ResourceThreadBase*>( This ) )->ThreadLoop();
159     return NULL;
160 }
161
162 void ResourceThreadBase::Pause()
163 {
164   ConditionalWait::ScopedLock lock( mCondition );
165   mPaused = true;
166 }
167
168 void ResourceThreadBase::Resume()
169 {
170   // Clear the paused flag and if we weren't running already, also wake up the background thread:
171   bool wasPaused = false;
172   {
173     ConditionalWait::ScopedLock lock( mCondition );
174     wasPaused = mPaused;
175     mPaused = false;
176   }
177
178   // If we were paused, wake up the background thread and give it a
179   // chance to do some work:
180   if( wasPaused )
181   {
182     mCondition.Notify();
183   }
184 }
185
186 //----------------- Called from separate thread (mThread) -----------------
187
188 void ResourceThreadBase::ThreadLoop()
189 {
190   // TODO: Use Environment Options
191   const char* threadPriorityIdleRequired = std::getenv( IDLE_PRIORITY_ENVIRONMENT_VARIABLE_NAME );
192   if( threadPriorityIdleRequired )
193   {
194     // if the parameter exists then set up an idle priority for this thread
195     struct sched_param sp;
196     sp.sched_priority = 0;
197     sched_setscheduler(0, SCHED_IDLE, &sp);
198     ///@ToDo: change to the corresponding Pthreads call, not this POSIX.1-2001 one with a Linux-specific argument (SCHED_IDLE): int pthread_setschedparam(pthread_t thread, int policy, const struct sched_param *param);, as specified in the docs for sched_setscheduler(): http://man7.org/linux/man-pages/man2/sched_setscheduler.2.html
199   }
200
201   InstallLogging();
202
203   while( !mResourceLoader.IsTerminating() )
204   {
205     try
206     {
207       WaitForRequests();
208
209       if ( !mResourceLoader.IsTerminating() )
210       {
211         ProcessNextRequest();
212       }
213     }
214
215     catch( CancelRequestException& ex )
216     {
217       // No problem: a derived class deliberately threw to abort an in-flight request
218       // that was cancelled.
219       DALI_LOG_INFO( mLogFilter, Debug::Concise, "%s: Caught cancellation exception for resource (%u).\n", __FUNCTION__, unsigned(mCurrentRequestId) );
220       CancelRequestException* disableUnusedVarWarning = &ex;
221       ex = *disableUnusedVarWarning;
222     }
223
224     // Catch all exceptions to avoid killing the process, and log the error:
225     catch( std::exception& ex )
226     {
227       const char * const what = ex.what();
228       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" );
229     }
230     catch( Dali::DaliException& ex )
231     {
232       // Probably a failed assert-always:
233       DALI_LOG_ERROR( "DaliException caught in resource thread. Aborting request with id %u. Location: \"%s\". Condition: \"%s\".\n", unsigned(mCurrentRequestId), ex.location, ex.condition );
234     }
235     catch( ... )
236     {
237       DALI_LOG_ERROR( "Unknown exception caught in resource thread. Aborting request with id %u.\n", unsigned(mCurrentRequestId) );
238     }
239   }
240 }
241
242 void ResourceThreadBase::WaitForRequests()
243 {
244   ConditionalWait::ScopedLock lock( mCondition );
245
246   if( mQueue.empty() || mPaused == true )
247   {
248     // Waiting for a wake up from resource loader control thread
249     // This will be to process a new request or terminate
250     mCondition.Wait( lock );
251   }
252 }
253
254 void ResourceThreadBase::ProcessNextRequest()
255 {
256   ResourceRequest* request(NULL);
257   RequestType type(RequestLoad);
258
259   {
260     // lock the queue and extract the next request
261     ConditionalWait::ScopedLock lock( mCondition );
262
263     if (!mQueue.empty())
264     {
265       const RequestInfo & front = mQueue.front();
266       request = new ResourceRequest( front.first );
267       type = front.second;
268       mCurrentRequestId = front.first.GetId();
269       mQueue.pop_front();
270     }
271   } // unlock the queue
272
273   // process request outside of lock
274   if ( NULL != request )
275   {
276     std::auto_ptr<ResourceRequest> deleter( request );
277     switch( type )
278     {
279       case RequestLoad:
280       {
281         Load(*request);
282       }
283       break;
284
285       case RequestDownload:
286       {
287         Download(*request);
288       }
289       break;
290
291       case RequestDecode:
292       {
293         Decode(*request);
294       }
295       break;
296     }
297   }
298 }
299
300 void ResourceThreadBase::InstallLogging()
301 {
302   // resource loading thread will send its logs to TIZEN Platform's LogMessage handler.
303   Dali::Integration::Log::InstallLogFunction(Dali::TizenPlatform::LogMessage);
304 }
305
306 void ResourceThreadBase::UninstallLogging()
307 {
308   // uninstall it on resource loading thread.
309   Dali::Integration::Log::UninstallLogFunction();
310 }
311
312 void ResourceThreadBase::Download(const Integration::ResourceRequest& request)
313 {
314   DALI_LOG_TRACE_METHOD(mLogFilter);
315   DALI_LOG_WARNING("Resource Downloading from a remote server not supported for this type.");
316   ///! If you need this for a subclassed thread, look to ResourceThreadImage::Download() for an example implementation.
317 }
318
319 void ResourceThreadBase::Decode(const Integration::ResourceRequest& request)
320 {
321   DALI_LOG_TRACE_METHOD(mLogFilter);
322   DALI_LOG_WARNING("Resource Decoding from a memory buffer not supported for this type.");
323   ///! If you need this for a subclassed thread, look to ResourceThreadImage::Decode() for an example implementation.
324 }
325
326 } // namespace TizenPlatform
327
328 } // namespace Dali