Back to home page

EIC code displayed by LXR

 
 

    


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__