1 /*---------------------------------------------------------------------------*
2   Project:  Horizon
3   File:     osl_IpcIpcDispatcher.h
4 
5   Copyright (C)2009 Nintendo Co., Ltd.  All rights reserved.
6 
7   These coded instructions, statements, and computer programs contain
8   proprietary information of Nintendo of America Inc. and/or Nintendo
9   Company Ltd., and are protected by Federal copyright law.  They may
10   not be disclosed to third parties or copied or duplicated in any form,
11   in whole or in part, without the prior written consent of Nintendo.
12 
13   $Rev: 13955 $
14  *---------------------------------------------------------------------------*/
15 
16 #ifndef NN_NET_OSL_OSL_IPCDISPATCHER_H_
17 #define NN_NET_OSL_OSL_IPCDISPATCHER_H_
18 
19 #include <new>
20 #include <nn/srv.h>
21 #include <nn/os/os_Thread.h>
22 #include <nn/os/os_CriticalSection.h>
23 #include <nn/os/ipc/os_Port.h>
24 #include <nn/os/ipc/os_Session.h>
25 #include <nn/fnd/fnd_LinkedList.h>
26 #include <nn/fnd/fnd_Allocator.h>
27 
28 namespace nn {
29 namespace net {
30 namespace osl {
31 
32 class IpcDispatcherBase : public os::ipc::Port
33 {
34 public:
35     virtual Result Launch() = 0;
36 };
37 
38 class IpcDispatcherExecutor
39 {
40 public:
41     static const size_t DISPATCHERS_MAX  = 8;
42     static const size_t WAITOBJECTS_MAX = 8;
IpcDispatcherExecutor(IpcDispatcherBase * ppDispatchers[],s32 countDispatchers)43     IpcDispatcherExecutor(IpcDispatcherBase* ppDispatchers[], s32 countDispatchers)
44         : m_countDispatchers(countDispatchers)
45     {
46         NN_MIN_ASSERT(countDispatchers, 0);
47         NN_MAX_ASSERT(countDispatchers, DISPATCHERS_MAX - 1);
48 
49         for (s32 i = 0; i < m_countDispatchers; ++i)
50         {
51             NN_POINTER_ASSERT(ppDispatchers[i]);
52             m_ppDispatchers[i] = ppDispatchers[i];
53         }
54     }
55 
IpcDispatcherExecutor(IpcDispatcherBase * pDispatcher)56     IpcDispatcherExecutor(IpcDispatcherBase* pDispatcher)
57         : m_countDispatchers(1)
58     {
59         NN_POINTER_ASSERT(pDispatcher);
60         m_ppDispatchers[0] = pDispatcher;
61     }
62 
WaitAny(nn::os::WaitObject * ppObjects[],s32 countWaitObjects)63     s32 WaitAny(nn::os::WaitObject* ppObjects[], s32 countWaitObjects)
64     {
65         NN_MIN_ASSERT(countWaitObjects, 0);
66         NN_MAX_ASSERT(countWaitObjects, WAITOBJECTS_MAX - 1);
67 
68         nn::os::WaitObject* ppObjectsToWait[DISPATCHERS_MAX + WAITOBJECTS_MAX];
69 
70         for (s32 i = 0; i < m_countDispatchers; ++i)
71         {
72             NN_POINTER_ASSERT(m_ppDispatchers[i]);
73             ppObjectsToWait[i] = m_ppDispatchers[i];
74         }
75         for (s32 i = 0; i < countWaitObjects; ++i)
76         {
77             NN_POINTER_ASSERT(ppObjects[i]);
78             ppObjectsToWait[m_countDispatchers + i] = ppObjects[i];
79         }
80 
81         s32 index;
82         while(true)
83         {
84             index = nn::os::WaitObject::WaitAny(ppObjectsToWait, m_countDispatchers + countWaitObjects);
85             if (index < m_countDispatchers)
86             {
87                 Result result = m_ppDispatchers[index]->Launch();
88                 if (result.IsFailure())
89                 {
90                     NN_LOG_WARN("IPC dispatch failed.\n");
91                     nn::dbg::PrintResult(result);
92                 }
93             }
94             else
95             {
96                 return index - m_countDispatchers;
97             }
98         }
99     }
100 
101 private:
102     IpcDispatcherBase*      m_ppDispatchers[DISPATCHERS_MAX];
103     s32                     m_countDispatchers;
104     s32                     m_countWaitObjects;
105     s32                     m_indexNotification;
106  };
107 
108 template <size_t StackSize, typename ImplT, Result (*pSessionLoopFunction)(s32*, ImplT*, Handle*, s32, s32)>
109 class IpcDispatcher : public IpcDispatcherBase
110 {
111     typedef IpcDispatcher<StackSize, ImplT, pSessionLoopFunction> Base;
112     class Worker : public fnd::IntrusiveLinkedList<Worker>::Item
113     {
114     public:
Worker(IpcDispatcher * pDispatcher)115         Worker(IpcDispatcher* pDispatcher)
116             : m_pDispatcher(pDispatcher)
117             , m_bInitialized(false)
118         {
119         };
120 
~Worker()121         ~Worker()
122         {
123             if (m_bInitialized)
124             {
125                 Finalize();
126             }
127         }
128 
IsInitialized(void)129         inline bool IsInitialized(void) const
130         {
131             return m_bInitialized;
132         }
133 
134         Result TryInitialize(os::ipc::Port& port, s32 priority = os::DEFAULT_THREAD_PRIORITY)
135         {
136             NN_ASSERT(!m_bInitialized);
137 
138             Result result;
139 
140             result = port.TryAccept(&m_session, false);
141             NN_UTIL_RETURN_IF_FAILED(result);
142 
143             result = m_thread.TryStart<Worker*, Worker*, StackT>(SessionThread, this, m_stack, priority);
144             if (result.IsFailure())
145             {
146                 m_session.Close();
147                 return result;
148             }
149 
150             m_bInitialized = true;
151             return ResultSuccess();
152         }
153 
Finalize(void)154         void Finalize(void)
155         {
156             NN_ASSERT(m_bInitialized);
157             Wait();
158             m_thread.Finalize();
159             m_bInitialized = false;
160         }
161 
Wait(void)162         void Wait(void)
163         {
164             m_thread.Join();
165         }
166 
new(size_t size,nn::fnd::IAllocator & allocator)167         static void* operator new(size_t size, nn::fnd::IAllocator& allocator) throw()
168         {
169             return allocator.Allocate(size, sizeof(u64));
170         }
171 
delete(void * p)172         static void operator delete(void* p) throw()
173         {
174             NN_ASSERT(p && reinterpret_cast<Worker*>(p)->m_pDispatcher);
175             reinterpret_cast<Worker*>(p)->m_pDispatcher->m_allocator.Free(p);
176         }
177 
178     private:
SessionThread(Worker * pWorker)179         static void SessionThread(Worker* pWorker)
180         {
181             pWorker->SessionThreadImpl();
182         }
183 
SessionThreadImpl(void)184         void SessionThreadImpl(void)
185         {
186             ImplT* pImpl = reinterpret_cast<ImplT*>(&m_implStorage);
187             new (pImpl) ImplT();
188             {
189                 s32 resultIndex;
190                 Handle handles[] = { m_session.GetHandle() };
191                 Result result = pSessionLoopFunction(&resultIndex, pImpl, handles, 1, 0);
192                 if (result.IsFailure() && result.GetDescription() != nn::os::DESCRIPTION_SESSION_CLOSED)
193                 {
194                     NN_UTIL_PANIC_IF_FAILED(result);
195                 }
196             }
197             pImpl->~ImplT();
198 
199             m_session.Close();
200             m_pDispatcher->TakeBackFreeWorker(this);
201         }
202 
203         typedef nn::os::StackBuffer<StackSize> StackT;
204 
205         StackT                  m_stack;
206         u64                     m_implStorage[sizeof(ImplT)/sizeof(u64) + 1];
207         nn::os::Thread          m_thread;
208         nn::os::ipc::Session    m_session;
209         IpcDispatcher*          m_pDispatcher;
210         bool                    m_bInitialized;
211         u8                      padding[7];
212     };
213 
214     typedef fnd::IntrusiveLinkedList<Worker> WorkerList;
215     friend class Worker;
216 
217 public:
218     IpcDispatcher(const char* pServiceName,
219                   nn::fnd::IAllocator& allocator,
220                   s32 maxThreads = 1, s32 priority = os::DEFAULT_THREAD_PRIORITY)
m_cs(nn::WithInitialize ())221         : m_cs(nn::WithInitialize())
222         , m_allocator(allocator)
223         , m_maxThreads(maxThreads)
224         , m_priority(priority)
225         , m_pServiceName(pServiceName)
226     {
227         Result result;
228         Handle hPort;
229 
230         ConstructWorkerList(maxThreads);
231 
232         NN_LOG_INFO("Registering service: %s\n", pServiceName);
233         result = nn::srv::RegisterService(&hPort, pServiceName);
234         NN_UTIL_PANIC_IF_FAILED(result);
235 
236         WaitObject::SetHandle(hPort);
237     }
238 
~IpcDispatcher()239     ~IpcDispatcher()
240     {
241         if (IsValid())
242         {
243             Finalize();
244         }
245     }
246 
Finalize(void)247     void Finalize(void)
248     {
249         Result result;
250 
251         DestructWorkerList();
252 
253         result = nn::srv::UnregisterService(m_pServiceName);
254         NN_UTIL_PANIC_IF_FAILED(result);
255 
256         WaitObject::Close();
257     }
258 
Launch(void)259     virtual Result Launch(void)
260     {
261         NN_LOG_INFO("Accepting %s\n", m_pServiceName);
262 
263         Worker* pWorker = GetFreeWorker();
264         if (!pWorker)
265         {
266             NN_LOG_WARN("No free worker.\n");
267             nn::os::Thread::Sleep(nn::fnd::TimeSpan::FromMilliSeconds(1000));
268             return nn::MakeStatusResult(nn::Result::SUMMARY_OUT_OF_RESOURCE, nn::Result::MODULE_NN_OS, nn::Result::DESCRIPTION_OUT_OF_MEMORY);
269         }
270 
271         Result result = pWorker->TryInitialize(*this, m_priority);
272         if (result.IsFailure())
273         {
274             TakeBackFreeWorker(pWorker);
275             return result;
276         }
277         return ResultSuccess();
278     }
279 
280 private:
GetFreeWorker(void)281     Worker* GetFreeWorker(void)
282     {
283         os::CriticalSection::ScopedLock locker(m_cs);
284         if (m_freeList.IsEmpty())
285         {
286             return NULL;
287         }
288         Worker* pWorker = m_freeList.PopFront();
289         m_activeList.PushBack(pWorker);
290 
291         // 使用済みなら一回 Finalized しておく
292         if (pWorker->IsInitialized())
293         {
294             pWorker->Finalize();
295         }
296         return pWorker;
297     }
298 
TakeBackFreeWorker(Worker * pWorker)299     void TakeBackFreeWorker(Worker* pWorker)
300     {
301         os::CriticalSection::ScopedLock locker(m_cs);
302 
303         // このコンテキストで Join するとデッドロックするので改めて使うときに Finalize する
304         m_activeList.Erase(pWorker);
305         m_freeList.PushBack(pWorker);
306     }
307 
ConstructWorkerList(s32 maxThreads)308     void ConstructWorkerList(s32 maxThreads)
309     {
310         NN_ASSERT(maxThreads > 0);
311 
312         for(s32 i = 0; i < maxThreads; ++i)
313         {
314             Worker* pWorker = new (m_allocator) Worker(this);
315             if (pWorker == NULL)
316             {
317                 NN_LOG_WARN("Failed to allocate memory for Woker.(%d/%d)", i, maxThreads);
318                 NN_ASSERT(pWorker);
319                 break;
320             }
321             m_freeList.PushBack(pWorker);
322         }
323     }
324 
DestructWorkerList(void)325     void DestructWorkerList(void)
326     {
327         os::CriticalSection::ScopedLock locker(m_cs);
328 
329         while(!m_freeList.IsEmpty() || !m_activeList.IsEmpty())
330         {
331             while(!m_freeList.IsEmpty())
332             {
333                 Worker* pWorker = m_freeList.PopFront();
334                 delete pWorker;
335             }
336 
337             if(!m_activeList.IsEmpty())
338             {
339                 Worker* pWorker = m_activeList.GetFront();
340                 {
341                     m_cs.Leave();
342                     pWorker->Wait();
343                     m_cs.Enter();
344                 }
345             }
346         }
347 
348    }
349     nn::os::CriticalSection m_cs;
350     nn::fnd::IAllocator&    m_allocator;
351     WorkerList              m_freeList;
352     WorkerList              m_activeList;
353     s32                     m_maxThreads;
354     s32                     m_priority;
355     const char*             m_pServiceName;
356 };
357 
358 } // end of namespace osl
359 } // end of namespace net
360 } // end of namespace nn
361 
362 #endif // NN_NET_OSL_OSL_IPCDISPATCHER_H_
363