|
|
|||
File indexing completed on 2026-09-19 09:41:44
0001 //------------------------------------------------------------------------------ 0002 // Copyright (c) 2011-2017 by European Organization for Nuclear Research (CERN) 0003 // Author: Krzysztof Jamrog <krzysztof.piotr.jamrog@cern.ch>, 0004 // Michal Simon <michal.simon@cern.ch> 0005 //------------------------------------------------------------------------------ 0006 // This file is part of the XRootD software suite. 0007 // 0008 // XRootD is free software: you can redistribute it and/or modify 0009 // it under the terms of the GNU Lesser General Public License as published by 0010 // the Free Software Foundation, either version 3 of the License, or 0011 // (at your option) any later version. 0012 // 0013 // XRootD is distributed in the hope that it will be useful, 0014 // but WITHOUT ANY WARRANTY; without even the implied warranty of 0015 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0016 // GNU General Public License for more details. 0017 // 0018 // You should have received a copy of the GNU Lesser General Public License 0019 // along with XRootD. If not, see <http://www.gnu.org/licenses/>. 0020 // 0021 // In applying this licence, CERN does not waive the privileges and immunities 0022 // granted to it by virtue of its status as an Intergovernmental Organization 0023 // or submit itself to any jurisdiction. 0024 //------------------------------------------------------------------------------ 0025 0026 #ifndef __XRD_CL_PARALLELOPERATION_HH__ 0027 #define __XRD_CL_PARALLELOPERATION_HH__ 0028 0029 #include "XrdCl/XrdClOperations.hh" 0030 #include "XrdCl/XrdClOperationHandlers.hh" 0031 #include "XrdCl/XrdClDefaultEnv.hh" 0032 #include "XrdCl/XrdClPostMaster.hh" 0033 #include "XrdCl/XrdClJobManager.hh" 0034 0035 #include <atomic> 0036 #include <condition_variable> 0037 #include <mutex> 0038 0039 namespace XrdCl 0040 { 0041 0042 //---------------------------------------------------------------------------- 0043 // Interface for different execution policies: 0044 // - all : all operations need to succeed in order for the parallel 0045 // operation to be successful 0046 // - any : just one of the operations needs to succeed in order for 0047 // the parallel operation to be successful 0048 // - some : n (user defined) operations need to succeed in order for 0049 // the parallel operation to be successful 0050 // - at least : at least n (user defined) operations need to succeed in 0051 // order for the parallel operation to be successful (the 0052 // user handler will be called only when all operations are 0053 // resolved) 0054 // 0055 // @param status : status returned by one of the aggregated operations 0056 // 0057 // @return : true if the status should be passed to the user handler, 0058 // false otherwise. 0059 //---------------------------------------------------------------------------- 0060 struct PolicyExecutor 0061 { 0062 virtual ~PolicyExecutor() 0063 { 0064 } 0065 0066 virtual bool Examine( const XrdCl::XRootDStatus &status ) = 0; 0067 0068 virtual XRootDStatus Result() = 0; 0069 }; 0070 0071 //---------------------------------------------------------------------------- 0072 //! Parallel operations, allows to execute two or more pipelines in 0073 //! parallel. 0074 //! 0075 //! @arg state : describes current operation configuration state 0076 //! (@see Operation) 0077 //---------------------------------------------------------------------------- 0078 template<bool HasHndl> 0079 class ParallelOperation: public ConcreteOperation<ParallelOperation, HasHndl, Resp<void>> 0080 { 0081 template<bool> friend class ParallelOperation; 0082 0083 public: 0084 0085 //------------------------------------------------------------------------ 0086 //! Constructor: copy-move a ParallelOperation in different state 0087 //------------------------------------------------------------------------ 0088 template<bool from> 0089 ParallelOperation( ParallelOperation<from> &&obj ) : 0090 ConcreteOperation<ParallelOperation, HasHndl, Resp<void>>( std::move( obj ) ), 0091 pipelines( std::move( obj.pipelines ) ), 0092 policy( std::move( obj.policy ) ) 0093 { 0094 } 0095 0096 //------------------------------------------------------------------------ 0097 //! Constructor 0098 //! 0099 //! @arg Container : iterable container type 0100 //! 0101 //! @param container : iterable container with pipelines 0102 //------------------------------------------------------------------------ 0103 template<class Container> 0104 ParallelOperation( Container &&container ) 0105 { 0106 static_assert( !HasHndl, "Constructor is available only operation without handler"); 0107 0108 pipelines.reserve( container.size() ); 0109 auto begin = std::make_move_iterator( container.begin() ); 0110 auto end = std::make_move_iterator( container.end() ); 0111 std::copy( begin, end, std::back_inserter( pipelines ) ); 0112 container.clear(); // there's junk inside so we clear it 0113 } 0114 0115 ~ParallelOperation() 0116 { 0117 } 0118 0119 //------------------------------------------------------------------------ 0120 //! @return : operation name 0121 //------------------------------------------------------------------------ 0122 std::string ToString() 0123 { 0124 std::ostringstream oss; 0125 oss << "Parallel("; 0126 for( size_t i = 0; i < pipelines.size(); i++ ) 0127 { 0128 oss << pipelines[i]->ToString(); 0129 if( i + 1 != pipelines.size() ) 0130 { 0131 oss << " && "; 0132 } 0133 } 0134 oss << ")"; 0135 return oss.str(); 0136 } 0137 0138 //------------------------------------------------------------------------ 0139 //! Set policy to `All` (default) 0140 //! 0141 //! All operations need to succeed in order for the parallel operation to 0142 //! be successful. 0143 //------------------------------------------------------------------------ 0144 ParallelOperation<HasHndl> All() 0145 { 0146 policy.reset( new AllPolicy() ); 0147 return std::move( *this ); 0148 } 0149 0150 //------------------------------------------------------------------------ 0151 //! Set policy to `Any` 0152 //! 0153 //! Just one of the operations needs to succeed in order for the parallel 0154 //! operation to be successful. 0155 //------------------------------------------------------------------------ 0156 ParallelOperation<HasHndl> Any() 0157 { 0158 policy.reset( new AnyPolicy( pipelines.size() ) ); 0159 return std::move( *this ); 0160 } 0161 0162 //------------------------------------------------------------------------ 0163 // Set policy to `Some` 0164 //! 0165 //! n (user defined) operations need to succeed in order for the parallel 0166 //! operation to be successful. 0167 //------------------------------------------------------------------------ 0168 ParallelOperation<HasHndl> Some( size_t threshold ) 0169 { 0170 policy.reset( new SomePolicy( pipelines.size(), threshold ) ); 0171 return std::move( *this ); 0172 } 0173 0174 //------------------------------------------------------------------------ 0175 //! Set policy to `At Least`. 0176 //! 0177 //! At least n (user defined) operations need to succeed in order for the 0178 //! parallel operation to be successful (the user handler will be called 0179 //! only when all operations are resolved). 0180 //------------------------------------------------------------------------ 0181 ParallelOperation<HasHndl> AtLeast( size_t threshold ) 0182 { 0183 policy.reset( new AtLeastPolicy( pipelines.size(), threshold ) ); 0184 return std::move( *this ); 0185 } 0186 0187 private: 0188 0189 //------------------------------------------------------------------------ 0190 //! `All` policy implementation 0191 //! 0192 //! All operations need to succeed in order for the parallel operation to 0193 //! be successful. 0194 //------------------------------------------------------------------------ 0195 struct AllPolicy : public PolicyExecutor 0196 { 0197 bool Examine( const XrdCl::XRootDStatus &status ) 0198 { 0199 // keep the status in case this is the final result 0200 res = status; 0201 if( status.IsOK() ) return false; 0202 // we require all request to succeed 0203 return true; 0204 } 0205 0206 XRootDStatus Result() 0207 { 0208 return res; 0209 } 0210 0211 XRootDStatus res; 0212 }; 0213 0214 //------------------------------------------------------------------------ 0215 //! `Any` policy implementation 0216 //! 0217 //! Just one of the operations needs to succeed in order for the parallel 0218 //! operation to be successful. 0219 //------------------------------------------------------------------------ 0220 struct AnyPolicy : public PolicyExecutor 0221 { 0222 AnyPolicy( size_t size) : cnt( size ) 0223 { 0224 } 0225 0226 bool Examine( const XrdCl::XRootDStatus &status ) 0227 { 0228 // keep the status in case this is the final result 0229 res = status; 0230 // decrement the counter 0231 size_t nb = cnt.fetch_sub( 1, std::memory_order_relaxed ); 0232 // we require just one operation to be successful 0233 if( status.IsOK() ) return true; 0234 // lets see if this is the last one? 0235 if( nb == 1 ) return true; 0236 // we still have a chance there will be one that is successful 0237 return false; 0238 } 0239 0240 XRootDStatus Result() 0241 { 0242 return res; 0243 } 0244 0245 private: 0246 std::atomic<size_t> cnt; 0247 XRootDStatus res; 0248 }; 0249 0250 //------------------------------------------------------------------------ 0251 //! `Some` policy implementation 0252 //! 0253 //! n (user defined) operations need to succeed in order for the parallel 0254 //! operation to be successful. 0255 //------------------------------------------------------------------------ 0256 struct SomePolicy : PolicyExecutor 0257 { 0258 SomePolicy( size_t size, size_t threshold ) : failed( 0 ), succeeded( 0 ), 0259 threshold( threshold ), size( size ) 0260 { 0261 } 0262 0263 bool Examine( const XrdCl::XRootDStatus &status ) 0264 { 0265 // keep the status in case this is the final result 0266 res = status; 0267 if( status.IsOK() ) 0268 { 0269 size_t s = succeeded.fetch_add( 1, std::memory_order_relaxed ); 0270 if( s + 1 == threshold ) return true; // we reached the threshold 0271 // we are not yet there 0272 return false; 0273 } 0274 size_t f = failed.fetch_add( 1, std::memory_order_relaxed ); 0275 // did we drop below the threshold 0276 if( f == size - threshold ) return true; 0277 // we still have a chance there will be enough of successful operations 0278 return false; 0279 } 0280 0281 XRootDStatus Result() 0282 { 0283 return res; 0284 } 0285 0286 private: 0287 std::atomic<size_t> failed; 0288 std::atomic<size_t> succeeded; 0289 const size_t threshold; 0290 const size_t size; 0291 XRootDStatus res; 0292 }; 0293 0294 //------------------------------------------------------------------------ 0295 //! `At Least` policy implementation 0296 //! 0297 //! At least n (user defined) operations need to succeed in order for the 0298 //! parallel operation to be successful (the user handler will be called 0299 //! only when all operations are resolved). 0300 //------------------------------------------------------------------------ 0301 struct AtLeastPolicy : PolicyExecutor 0302 { 0303 AtLeastPolicy( size_t size, size_t threshold ) : pending_cnt( size ), 0304 failed_cnt( 0 ), 0305 failed_threshold( size - threshold ) 0306 { 0307 } 0308 0309 //---------------------------------------------------------------------- 0310 //! Examines a @p status in the pipeline. 0311 //! 0312 //! @returns Returns true if there are no more pending operations to examine. 0313 //---------------------------------------------------------------------- 0314 bool Examine( const XrdCl::XRootDStatus &status ) 0315 { 0316 if (!status.IsOK()) { 0317 if (failed_cnt.fetch_add(1, std::memory_order_relaxed) == failed_threshold) { 0318 res = status; 0319 return true; 0320 } 0321 } 0322 0323 return pending_cnt.fetch_sub(1, std::memory_order_relaxed) == 1; 0324 } 0325 0326 XRootDStatus Result() 0327 { 0328 return res; 0329 } 0330 0331 private: 0332 std::atomic<size_t> pending_cnt; 0333 std::atomic<size_t> failed_cnt; 0334 const size_t failed_threshold; 0335 XRootDStatus res; 0336 }; 0337 0338 //------------------------------------------------------------------------ 0339 //! A wait barrier helper class 0340 //------------------------------------------------------------------------ 0341 struct barrier_t 0342 { 0343 barrier_t() : on( true ) { } 0344 0345 void wait() 0346 { 0347 std::unique_lock<std::mutex> lck( mtx ); 0348 if( on ) cv.wait( lck ); 0349 } 0350 0351 void lift() 0352 { 0353 std::unique_lock<std::mutex> lck( mtx ); 0354 on = false; 0355 cv.notify_all(); 0356 } 0357 0358 private: 0359 std::condition_variable cv; 0360 std::mutex mtx; 0361 bool on; 0362 }; 0363 0364 //------------------------------------------------------------------------ 0365 //! Helper class for handling the PipelineHandler of the 0366 //! ParallelOperation (RAII). 0367 //! 0368 //! Guarantees that the handler will be executed exactly once. 0369 //------------------------------------------------------------------------ 0370 struct Ctx 0371 { 0372 //---------------------------------------------------------------------- 0373 //! Constructor. 0374 //! 0375 //! @param handler : the PipelineHandler of the Parallel operation 0376 //---------------------------------------------------------------------- 0377 Ctx( PipelineHandler *handler, PolicyExecutor *policy ): handler( handler ), 0378 policy( policy ) 0379 { 0380 } 0381 0382 //---------------------------------------------------------------------- 0383 //! Destructor. 0384 //---------------------------------------------------------------------- 0385 ~Ctx() 0386 { 0387 Handle( XRootDStatus() ); 0388 } 0389 0390 //---------------------------------------------------------------------- 0391 //! Forwards the status to the PipelineHandler if the handler haven't 0392 //! been called yet. 0393 //! 0394 //! @param st : status 0395 //---------------------------------------------------------------------- 0396 inline void Examine( const XRootDStatus &st ) 0397 { 0398 if( policy->Examine( st ) ) 0399 Handle( policy->Result() ); 0400 } 0401 0402 //---------------------------------------------------------------------- 0403 //! Forwards the status to the PipelineHandler if the handler haven't 0404 //! been called yet. 0405 //! 0406 //! @param st : status 0407 //--------------------------------------------------------------------- 0408 inline void Handle( const XRootDStatus &st ) 0409 { 0410 PipelineHandler* hdlr = handler.exchange( nullptr, std::memory_order_relaxed ); 0411 if( hdlr ) 0412 { 0413 barrier.wait(); 0414 hdlr->HandleResponse( new XRootDStatus( st ), nullptr ); 0415 } 0416 } 0417 0418 //---------------------------------------------------------------------- 0419 //! PipelineHandler of the ParallelOperation 0420 //---------------------------------------------------------------------- 0421 std::atomic<PipelineHandler*> handler; 0422 0423 //---------------------------------------------------------------------- 0424 //! Policy defining when the user handler should be called 0425 //---------------------------------------------------------------------- 0426 std::unique_ptr<PolicyExecutor> policy; 0427 0428 //---------------------------------------------------------------------- 0429 //! wait barrier that assures handler is called only after RunImpl 0430 //! started all pipelines 0431 //---------------------------------------------------------------------- 0432 barrier_t barrier; 0433 }; 0434 0435 //------------------------------------------------------------------------ 0436 //! The thread-pool job for schedule Ctx::Examine 0437 //------------------------------------------------------------------------ 0438 struct PipelineEnd : public Job 0439 { 0440 //---------------------------------------------------------------------- 0441 // Constructor 0442 //---------------------------------------------------------------------- 0443 PipelineEnd( std::shared_ptr<Ctx> &ctx, 0444 const XrdCl::XRootDStatus &st ) : ctx( ctx ), st( st ) 0445 { 0446 } 0447 0448 //---------------------------------------------------------------------- 0449 // Run Ctx::Examine in the thread-pool 0450 //---------------------------------------------------------------------- 0451 void Run( void* ) 0452 { 0453 ctx->Examine( st ); 0454 delete this; 0455 } 0456 0457 private: 0458 std::shared_ptr<Ctx> ctx; //< ParallelOperaion context 0459 XrdCl::XRootDStatus st; //< final status of the ParallelOperation 0460 }; 0461 0462 //------------------------------------------------------------------------ 0463 //! Schedule Ctx::Examine to be executed in the client thread-pool 0464 //------------------------------------------------------------------------ 0465 inline static 0466 void Schedule( std::shared_ptr<Ctx> &ctx, const XrdCl::XRootDStatus &st) 0467 { 0468 XrdCl::JobManager *mgr = XrdCl::DefaultEnv::GetPostMaster()->GetJobManager(); 0469 PipelineEnd *end = new PipelineEnd( ctx, st ); 0470 mgr->QueueJob( end, nullptr ); 0471 } 0472 0473 //------------------------------------------------------------------------ 0474 //! Run operation 0475 //! 0476 //! @param params : container with parameters forwarded from 0477 //! previous operation 0478 //! @return : status of the operation 0479 //------------------------------------------------------------------------ 0480 XRootDStatus RunImpl( PipelineHandler *handler, uint16_t pipelineTimeout ) 0481 { 0482 // make sure we have a valid policy for the parallel operation 0483 if( !policy ) policy.reset( new AllPolicy() ); 0484 0485 std::shared_ptr<Ctx> ctx = 0486 std::make_shared<Ctx>( handler, policy.release() ); 0487 0488 uint16_t timeout = pipelineTimeout < this->timeout ? 0489 pipelineTimeout : this->timeout; 0490 0491 for( size_t i = 0; i < pipelines.size(); ++i ) 0492 { 0493 if( !pipelines[i] ) continue; 0494 pipelines[i].Run( timeout, 0495 [ctx]( const XRootDStatus &st ) mutable { Schedule( ctx, st ); } ); 0496 } 0497 0498 ctx->barrier.lift(); 0499 return XRootDStatus(); 0500 } 0501 0502 std::vector<Pipeline> pipelines; 0503 std::unique_ptr<PolicyExecutor> policy; 0504 }; 0505 0506 //---------------------------------------------------------------------------- 0507 //! Factory function for creating parallel operation from a vector 0508 //---------------------------------------------------------------------------- 0509 template<class Container> 0510 inline ParallelOperation<false> Parallel( Container &&container ) 0511 { 0512 return ParallelOperation<false>( container ); 0513 } 0514 0515 //---------------------------------------------------------------------------- 0516 //! Helper function for converting parameter pack into a vector 0517 //---------------------------------------------------------------------------- 0518 inline void PipesToVec( std::vector<Pipeline>& ) 0519 { 0520 // base case 0521 } 0522 0523 //---------------------------------------------------------------------------- 0524 // Declare PipesToVec (we need to do declare those functions ahead of 0525 // definitions, as they may call each other. 0526 //---------------------------------------------------------------------------- 0527 template<typename ... Others> 0528 inline void PipesToVec( std::vector<Pipeline> &v, Operation<false> &operation, 0529 Others&... others ); 0530 0531 template<typename ... Others> 0532 inline void PipesToVec( std::vector<Pipeline> &v, Operation<true> &operation, 0533 Others&... others ); 0534 0535 template<typename ... Others> 0536 inline void PipesToVec( std::vector<Pipeline> &v, Pipeline &pipeline, 0537 Others&... others ); 0538 0539 //---------------------------------------------------------------------------- 0540 // Define PipesToVec 0541 //---------------------------------------------------------------------------- 0542 template<typename ... Others> 0543 void PipesToVec( std::vector<Pipeline> &v, Operation<false> &operation, 0544 Others&... others ) 0545 { 0546 v.emplace_back( operation ); 0547 PipesToVec( v, others... ); 0548 } 0549 0550 template<typename ... Others> 0551 void PipesToVec( std::vector<Pipeline> &v, Operation<true> &operation, 0552 Others&... others ) 0553 { 0554 v.emplace_back( operation ); 0555 PipesToVec( v, others... ); 0556 } 0557 0558 template<typename ... Others> 0559 void PipesToVec( std::vector<Pipeline> &v, Pipeline &pipeline, 0560 Others&... others ) 0561 { 0562 v.emplace_back( std::move( pipeline ) ); 0563 PipesToVec( v, others... ); 0564 } 0565 0566 //---------------------------------------------------------------------------- 0567 //! Factory function for creating parallel operation from 0568 //! a given number of operations 0569 //! (we use && reference since due to reference collapsing this will fit 0570 //! both r- and l-value references) 0571 //---------------------------------------------------------------------------- 0572 template<typename ... Operations> 0573 inline ParallelOperation<false> Parallel( Operations&& ... operations ) 0574 { 0575 constexpr size_t size = sizeof...( operations ); 0576 std::vector<Pipeline> v; 0577 v.reserve( size ); 0578 PipesToVec( v, operations... ); 0579 return Parallel( v ); 0580 } 0581 } 0582 0583 #endif // __XRD_CL_OPERATIONS_HH__
| [ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
|
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |
|