panda-server-source 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- panda_server_source-0.8.0.dist-info/METADATA +55 -0
- panda_server_source-0.8.0.dist-info/RECORD +375 -0
- panda_server_source-0.8.0.dist-info/WHEEL +4 -0
- panda_server_source-0.8.0.dist-info/licenses/LICENSE.txt +202 -0
- pandajedi/__init__.py +0 -0
- pandajedi/jedibrokerage/AtlasAnalJobBroker.py +2343 -0
- pandajedi/jedibrokerage/AtlasBrokerUtils.py +1292 -0
- pandajedi/jedibrokerage/AtlasProdJobBroker.py +2064 -0
- pandajedi/jedibrokerage/AtlasProdTaskBroker.py +629 -0
- pandajedi/jedibrokerage/GenJobBroker.py +381 -0
- pandajedi/jedibrokerage/JobBrokerBase.py +133 -0
- pandajedi/jedibrokerage/TaskBrokerBase.py +15 -0
- pandajedi/jedibrokerage/__init__.py +0 -0
- pandajedi/jediconfig/__init__.py +0 -0
- pandajedi/jediconfig/jedi_config.py +40 -0
- pandajedi/jedicore/FactoryBase.py +182 -0
- pandajedi/jedicore/Interaction.py +433 -0
- pandajedi/jedicore/JediDBProxy.py +56 -0
- pandajedi/jedicore/JediDBProxyPool.py +37 -0
- pandajedi/jedicore/JediDBSchemaInfo.py +17 -0
- pandajedi/jedicore/JediException.py +13 -0
- pandajedi/jedicore/JediTaskBuffer.py +984 -0
- pandajedi/jedicore/JediTaskBufferInterface.py +50 -0
- pandajedi/jedicore/MsgWrapper.py +124 -0
- pandajedi/jedicore/ProcessUtils.py +34 -0
- pandajedi/jedicore/SiteCandidate.py +90 -0
- pandajedi/jedicore/ThreadUtils.py +205 -0
- pandajedi/jedicore/__init__.py +0 -0
- pandajedi/jediddm/AtlasDDMClient.py +1708 -0
- pandajedi/jediddm/DDMClientBase.py +16 -0
- pandajedi/jediddm/DDMInterface.py +83 -0
- pandajedi/jediddm/GenDDMClient.py +17 -0
- pandajedi/jediddm/__init__.py +0 -0
- pandajedi/jedidog/AtlasAnalWatchDog.py +648 -0
- pandajedi/jedidog/AtlasDataCarouselWatchDog.py +189 -0
- pandajedi/jedidog/AtlasDataLocalityUpdaterWatchDog.py +215 -0
- pandajedi/jedidog/AtlasProdWatchDog.py +443 -0
- pandajedi/jedidog/AtlasQueueFillerWatchDog.py +800 -0
- pandajedi/jedidog/AtlasTaskWithholderWatchDog.py +245 -0
- pandajedi/jedidog/AtlasWorkflowManagerWatchDog.py +63 -0
- pandajedi/jedidog/GenWatchDog.py +21 -0
- pandajedi/jedidog/JumboWatchDog.py +147 -0
- pandajedi/jedidog/TypicalWatchDogBase.py +122 -0
- pandajedi/jedidog/WatchDogBase.py +53 -0
- pandajedi/jedidog/__init__.py +0 -0
- pandajedi/jediexec/reniceJEDI.py +25 -0
- pandajedi/jedigen/AtlasTaskGenerator.py +110 -0
- pandajedi/jedigen/TaskGeneratorBase.py +15 -0
- pandajedi/jedigen/__init__.py +0 -0
- pandajedi/jedimsgprocessor/__init__.py +0 -0
- pandajedi/jedimsgprocessor/ai_parameter_adjuster_msg_processor +71 -0
- pandajedi/jedimsgprocessor/atlas_idds_msg_processor.py +66 -0
- pandajedi/jedimsgprocessor/base_msg_processor.py +33 -0
- pandajedi/jedimsgprocessor/forwarding_msg_processor.py +29 -0
- pandajedi/jedimsgprocessor/hpo_msg_processor.py +93 -0
- pandajedi/jedimsgprocessor/jedi_contents_feeder_msg_processor.py +63 -0
- pandajedi/jedimsgprocessor/jedi_job_generator_msg_processor.py +185 -0
- pandajedi/jedimsgprocessor/jedi_post_processor_msg_processor.py +82 -0
- pandajedi/jedimsgprocessor/kafka_msg_processor.py +44 -0
- pandajedi/jedimsgprocessor/panda_callback_msg_processor.py +182 -0
- pandajedi/jedimsgprocessor/panda_to_jedi_msg_processor.py +74 -0
- pandajedi/jedimsgprocessor/processing_msg_processor.py +134 -0
- pandajedi/jedimsgprocessor/status_report_msg_processor.py +129 -0
- pandajedi/jedimsgprocessor/tape_carousel_msg_processor.py +118 -0
- pandajedi/jedimsgprocessor/workflow_manager_msg_processor.py +89 -0
- pandajedi/jediorder/ContentsFeeder.py +785 -0
- pandajedi/jediorder/JediDaemon.py +27 -0
- pandajedi/jediorder/JediKnight.py +55 -0
- pandajedi/jediorder/JediMaster.py +238 -0
- pandajedi/jediorder/JediMsgProcessor.py +32 -0
- pandajedi/jediorder/JobBroker.py +33 -0
- pandajedi/jediorder/JobGenerator.py +2763 -0
- pandajedi/jediorder/JobSplitter.py +308 -0
- pandajedi/jediorder/JobThrottler.py +28 -0
- pandajedi/jediorder/PostProcessor.py +166 -0
- pandajedi/jediorder/TaskBroker.py +268 -0
- pandajedi/jediorder/TaskCommando.py +334 -0
- pandajedi/jediorder/TaskGenerator.py +17 -0
- pandajedi/jediorder/TaskRefiner.py +611 -0
- pandajedi/jediorder/TaskSetupper.py +17 -0
- pandajedi/jediorder/WatchDog.py +79 -0
- pandajedi/jediorder/__init__.py +0 -0
- pandajedi/jedipprocess/AtlasAnalPostProcessor.py +404 -0
- pandajedi/jedipprocess/AtlasPostProcessorUtils.py +50 -0
- pandajedi/jedipprocess/AtlasProdPostProcessor.py +178 -0
- pandajedi/jedipprocess/GenPostProcessor.py +35 -0
- pandajedi/jedipprocess/MailTemplates.py +185 -0
- pandajedi/jedipprocess/PostProcessorBase.py +269 -0
- pandajedi/jedipprocess/__init__.py +0 -0
- pandajedi/jedirefine/AtlasAnalTaskRefiner.py +259 -0
- pandajedi/jedirefine/AtlasProdTaskRefiner.py +162 -0
- pandajedi/jedirefine/GenTaskRefiner.py +61 -0
- pandajedi/jedirefine/RefinerUtils.py +154 -0
- pandajedi/jedirefine/TaskRefinerBase.py +996 -0
- pandajedi/jedirefine/__init__.py +0 -0
- pandajedi/jedisetup/AtlasTaskSetupper.py +309 -0
- pandajedi/jedisetup/GenTaskSetupper.py +16 -0
- pandajedi/jedisetup/SimpleTaskSetupper.py +152 -0
- pandajedi/jedisetup/TaskSetupperBase.py +15 -0
- pandajedi/jedisetup/__init__.py +0 -0
- pandajedi/jeditest/SchemaChecker.py +41 -0
- pandajedi/jeditest/addContWoSoTask.py +54 -0
- pandajedi/jeditest/addDataCarouselRequest.py +47 -0
- pandajedi/jeditest/addEventServiceTask.py +101 -0
- pandajedi/jeditest/addHpoTask.py +81 -0
- pandajedi/jeditest/addIDDSStageInRequest.py +56 -0
- pandajedi/jeditest/addNonAtlasTask.py +71 -0
- pandajedi/jeditest/addNonAtlasTaskEnc.py +64 -0
- pandajedi/jeditest/addTestTask.py +124 -0
- pandajedi/jeditest/addTestTaskParamToDEFT.py +70 -0
- pandajedi/jeditest/addTestTaskWithMerger.py +97 -0
- pandajedi/jeditest/brokerTest.py +78 -0
- pandajedi/jeditest/cancelDataCarouselRequest.py +35 -0
- pandajedi/jeditest/contentsFeederTest.py +28 -0
- pandajedi/jeditest/contentsFeederTestWithTask.py +18 -0
- pandajedi/jeditest/ddmTest.py +4 -0
- pandajedi/jeditest/jobGenForOneTask.py +38 -0
- pandajedi/jeditest/jobGeneratorTest.py +43 -0
- pandajedi/jeditest/jobSplitterTest.py +104 -0
- pandajedi/jeditest/jobThrottlerTest.py +22 -0
- pandajedi/jeditest/msgProcessorTest.py +9 -0
- pandajedi/jeditest/postProcessorTest.py +18 -0
- pandajedi/jeditest/resubmitDataCarouselRequest.py +35 -0
- pandajedi/jeditest/resubmitTaskParam.py +20 -0
- pandajedi/jeditest/taskBrokerTest.py +22 -0
- pandajedi/jeditest/taskBrokerTestWithTask.py +38 -0
- pandajedi/jeditest/taskBufferTest.py +5 -0
- pandajedi/jeditest/taskCommandoTest.py +28 -0
- pandajedi/jeditest/taskRefinerTest.py +30 -0
- pandajedi/jeditest/testOneTaskToRefine.py +49 -0
- pandajedi/jeditest/testOneTaskToTaskBroker.py +42 -0
- pandajedi/jeditest/testPandaDDMMap.py +62 -0
- pandajedi/jeditest/updateDataCarouselRequestParams.py +50 -0
- pandajedi/jeditest/watchDogTest.py +25 -0
- pandajedi/jeditest/workQueueTest.py +22 -0
- pandajedi/jedithrottle/AtlasAnalJobThrottler.py +19 -0
- pandajedi/jedithrottle/AtlasProdJobThrottler.py +18 -0
- pandajedi/jedithrottle/GenJobThrottler.py +26 -0
- pandajedi/jedithrottle/JobThrottlerBase.py +496 -0
- pandajedi/jedithrottle/__init__.py +0 -0
- pandaserver/__init__.py +0 -0
- pandaserver/api/__init__.py +0 -0
- pandaserver/api/v1/__init__.py +0 -0
- pandaserver/api/v1/common.py +339 -0
- pandaserver/api/v1/credential_management_api.py +372 -0
- pandaserver/api/v1/data_carousel_api.py +264 -0
- pandaserver/api/v1/event_api.py +269 -0
- pandaserver/api/v1/file_server_api.py +942 -0
- pandaserver/api/v1/harvester_api.py +445 -0
- pandaserver/api/v1/http_client.py +166 -0
- pandaserver/api/v1/idds_api.py +180 -0
- pandaserver/api/v1/job_api.py +463 -0
- pandaserver/api/v1/metaconfig_api.py +122 -0
- pandaserver/api/v1/pilot_api.py +895 -0
- pandaserver/api/v1/statistics_api.py +207 -0
- pandaserver/api/v1/system_api.py +186 -0
- pandaserver/api/v1/task_api.py +1520 -0
- pandaserver/api/v1/tests/__init__.py +0 -0
- pandaserver/api/v1/tests/credential_management_api_tests.py +116 -0
- pandaserver/api/v1/tests/data_carousel_api_tests.py +82 -0
- pandaserver/api/v1/tests/harvester_api_tests.py +188 -0
- pandaserver/api/v1/tests/idds_api_tests.py +45 -0
- pandaserver/api/v1/tests/job_api_tests.py +112 -0
- pandaserver/api/v1/tests/pilot_api_tests.py +154 -0
- pandaserver/api/v1/tests/statistics_api_tests.py +104 -0
- pandaserver/api/v1/tests/system_api_tests.py +58 -0
- pandaserver/api/v1/tests/task_api_tests.py +590 -0
- pandaserver/api/v1/version.py +2 -0
- pandaserver/api/v1/workflow_api.py +136 -0
- pandaserver/brokerage/SiteMapper.py +406 -0
- pandaserver/brokerage/__init__.py +0 -0
- pandaserver/brokerage/broker.py +272 -0
- pandaserver/config/__init__.py +0 -0
- pandaserver/config/config_utils.py +15 -0
- pandaserver/config/daemon_config.py +29 -0
- pandaserver/config/mcp_config.py +38 -0
- pandaserver/config/panda_config.py +233 -0
- pandaserver/configurator/Carbon.py +65 -0
- pandaserver/configurator/Configurator.py +908 -0
- pandaserver/configurator/__init__.py +0 -0
- pandaserver/configurator/aux.py +73 -0
- pandaserver/daemons/__init__.py +0 -0
- pandaserver/daemons/master.py +110 -0
- pandaserver/daemons/master_systemd.py +79 -0
- pandaserver/daemons/scripts/__init__.py +0 -0
- pandaserver/daemons/scripts/add_main.py +264 -0
- pandaserver/daemons/scripts/add_sub.py +221 -0
- pandaserver/daemons/scripts/cache_pilots.py +47 -0
- pandaserver/daemons/scripts/cache_schedconfig.py +286 -0
- pandaserver/daemons/scripts/carbon.py +50 -0
- pandaserver/daemons/scripts/configurator.py +94 -0
- pandaserver/daemons/scripts/copyArchive.py +1387 -0
- pandaserver/daemons/scripts/datasetManager.py +1154 -0
- pandaserver/daemons/scripts/dummy_test.py +25 -0
- pandaserver/daemons/scripts/evpPD2P.py +144 -0
- pandaserver/daemons/scripts/hs_scrapers.py +264 -0
- pandaserver/daemons/scripts/metric_collector.py +927 -0
- pandaserver/daemons/scripts/panda_activeusers_query.py +80 -0
- pandaserver/daemons/scripts/pilotStreaming.py +109 -0
- pandaserver/daemons/scripts/process_workflow_files_daemon.py +166 -0
- pandaserver/daemons/scripts/recover_lost_files_daemon.py +177 -0
- pandaserver/daemons/scripts/task_evaluator.py +405 -0
- pandaserver/daemons/scripts/tmpwatch.py +70 -0
- pandaserver/daemons/scripts/worker_synchronization.py +97 -0
- pandaserver/daemons/utils.py +753 -0
- pandaserver/dataservice/DataServiceUtils.py +323 -0
- pandaserver/dataservice/ErrorCode.py +25 -0
- pandaserver/dataservice/RecoverLostFilesCore.py +322 -0
- pandaserver/dataservice/__init__.py +0 -0
- pandaserver/dataservice/activator.py +79 -0
- pandaserver/dataservice/adder_atlas_plugin.py +1106 -0
- pandaserver/dataservice/adder_dummy_plugin.py +31 -0
- pandaserver/dataservice/adder_gen.py +954 -0
- pandaserver/dataservice/adder_plugin_base.py +27 -0
- pandaserver/dataservice/adder_result.py +75 -0
- pandaserver/dataservice/adder_simple_plugin.py +160 -0
- pandaserver/dataservice/closer.py +247 -0
- pandaserver/dataservice/closer_atlas_plugin.py +90 -0
- pandaserver/dataservice/ddm.py +1381 -0
- pandaserver/dataservice/ddm_handler.py +95 -0
- pandaserver/dataservice/dyn_data_distributer.py +824 -0
- pandaserver/dataservice/event_lookup_client_ei.py +77 -0
- pandaserver/dataservice/event_picker.py +268 -0
- pandaserver/dataservice/finisher.py +194 -0
- pandaserver/dataservice/setupper.py +225 -0
- pandaserver/dataservice/setupper_atlas_plugin.py +1851 -0
- pandaserver/dataservice/setupper_dummy_plugin.py +47 -0
- pandaserver/dataservice/setupper_plugin_base.py +67 -0
- pandaserver/jobdispatcher/DispatcherUtils.py +35 -0
- pandaserver/jobdispatcher/ErrorCode.py +19 -0
- pandaserver/jobdispatcher/JobDispatcher.py +1625 -0
- pandaserver/jobdispatcher/Protocol.py +443 -0
- pandaserver/jobdispatcher/Watcher.py +186 -0
- pandaserver/jobdispatcher/__init__.py +0 -0
- pandaserver/pandamcp/__init__.py +0 -0
- pandaserver/pandamcp/mcp_main.py +56 -0
- pandaserver/pandamcp/mcp_test_client.py +95 -0
- pandaserver/pandamcp/mcp_utils.py +74 -0
- pandaserver/proxycache/__init__.py +0 -0
- pandaserver/proxycache/panda_proxy_cache.py +240 -0
- pandaserver/proxycache/token_cache.py +141 -0
- pandaserver/server/.gacl +6 -0
- pandaserver/server/panda.py +539 -0
- pandaserver/srvcore/CoreUtils.py +572 -0
- pandaserver/srvcore/MailUtils.py +106 -0
- pandaserver/srvcore/__init__.py +0 -0
- pandaserver/srvcore/allowed_methods.py +100 -0
- pandaserver/srvcore/oidc_utils.py +152 -0
- pandaserver/srvcore/panda_request.py +196 -0
- pandaserver/srvcore/srv_msg_utils.py +42 -0
- pandaserver/taskbuffer/ConBridge.py +513 -0
- pandaserver/taskbuffer/DBProxyPool.py +95 -0
- pandaserver/taskbuffer/DataCarousel.py +3053 -0
- pandaserver/taskbuffer/DatasetSpec.py +137 -0
- pandaserver/taskbuffer/DdmSpec.py +201 -0
- pandaserver/taskbuffer/ErrorCode.py +87 -0
- pandaserver/taskbuffer/EventServiceUtils.py +396 -0
- pandaserver/taskbuffer/FileSpec.py +290 -0
- pandaserver/taskbuffer/GlobalShares.py +244 -0
- pandaserver/taskbuffer/HarvesterMetricsSpec.py +92 -0
- pandaserver/taskbuffer/Initializer.py +82 -0
- pandaserver/taskbuffer/InputChunk.py +1080 -0
- pandaserver/taskbuffer/JediCacheSpec.py +57 -0
- pandaserver/taskbuffer/JediDatasetSpec.py +588 -0
- pandaserver/taskbuffer/JediFileSpec.py +242 -0
- pandaserver/taskbuffer/JediTaskSpec.py +1687 -0
- pandaserver/taskbuffer/JobSpec.py +1006 -0
- pandaserver/taskbuffer/JobUtils.py +232 -0
- pandaserver/taskbuffer/NucleusSpec.py +116 -0
- pandaserver/taskbuffer/OraDBProxy.py +174 -0
- pandaserver/taskbuffer/PanDAMsgProcessor.py +8 -0
- pandaserver/taskbuffer/PandaDBSchemaInfo.py +18 -0
- pandaserver/taskbuffer/ParseJobXML.py +350 -0
- pandaserver/taskbuffer/PickleFileSpec.py +18 -0
- pandaserver/taskbuffer/PickleJobSpec.py +25 -0
- pandaserver/taskbuffer/PrioUtil.py +40 -0
- pandaserver/taskbuffer/ProcessGroups.py +111 -0
- pandaserver/taskbuffer/ResourceSpec.py +175 -0
- pandaserver/taskbuffer/SQLDumper.py +37 -0
- pandaserver/taskbuffer/SQLManager.py +60 -0
- pandaserver/taskbuffer/SiteSpec.py +304 -0
- pandaserver/taskbuffer/SupErrors.py +10 -0
- pandaserver/taskbuffer/TaskBuffer.py +2882 -0
- pandaserver/taskbuffer/TaskBufferInterface.py +163 -0
- pandaserver/taskbuffer/Utils.py +848 -0
- pandaserver/taskbuffer/WorkQueue.py +227 -0
- pandaserver/taskbuffer/WorkQueueMapper.py +203 -0
- pandaserver/taskbuffer/WorkerSpec.py +143 -0
- pandaserver/taskbuffer/WrappedCursor.py +487 -0
- pandaserver/taskbuffer/WrappedPickle.py +88 -0
- pandaserver/taskbuffer/WrappedPostgresConn.py +24 -0
- pandaserver/taskbuffer/__init__.py +0 -0
- pandaserver/taskbuffer/db_proxy_mods/OraDBProxy_structure.png +0 -0
- pandaserver/taskbuffer/db_proxy_mods/__init__.py +7 -0
- pandaserver/taskbuffer/db_proxy_mods/base_module.py +841 -0
- pandaserver/taskbuffer/db_proxy_mods/data_carousel_module.py +772 -0
- pandaserver/taskbuffer/db_proxy_mods/entity_module.py +3804 -0
- pandaserver/taskbuffer/db_proxy_mods/job_complex_module.py +5661 -0
- pandaserver/taskbuffer/db_proxy_mods/job_standalone_module.py +2640 -0
- pandaserver/taskbuffer/db_proxy_mods/metrics_module.py +874 -0
- pandaserver/taskbuffer/db_proxy_mods/misc_standalone_module.py +4272 -0
- pandaserver/taskbuffer/db_proxy_mods/task_complex_module.py +6172 -0
- pandaserver/taskbuffer/db_proxy_mods/task_event_module.py +4953 -0
- pandaserver/taskbuffer/db_proxy_mods/task_standalone_module.py +4999 -0
- pandaserver/taskbuffer/db_proxy_mods/task_utils_module.py +2063 -0
- pandaserver/taskbuffer/db_proxy_mods/worker_module.py +1944 -0
- pandaserver/taskbuffer/db_proxy_mods/workflow_module.py +832 -0
- pandaserver/taskbuffer/retryModule.py +552 -0
- pandaserver/taskbuffer/task_split_rules.py +308 -0
- pandaserver/taskbuffer/workflow_processor.py +249 -0
- pandaserver/taskbuffer/wrapped_oracle_conn.py +18 -0
- pandaserver/test/SchemaChecker.py +40 -0
- pandaserver/test/__init__.py +0 -0
- pandaserver/test/banUser.py +43 -0
- pandaserver/test/boostPrio.py +19 -0
- pandaserver/test/boostUser.py +35 -0
- pandaserver/test/callbackDDM.py +11 -0
- pandaserver/test/daod_on_demand.py +95 -0
- pandaserver/test/finishJob.py +184 -0
- pandaserver/test/finishTaskJEDI.py +11 -0
- pandaserver/test/getJobs.py +53 -0
- pandaserver/test/killJob.py +79 -0
- pandaserver/test/killJobLowPrio.py +99 -0
- pandaserver/test/killJobsInTask.py +56 -0
- pandaserver/test/killTask.py +80 -0
- pandaserver/test/killTaskJEDI.py +11 -0
- pandaserver/test/killUser.py +83 -0
- pandaserver/test/lsst/README.txt +7 -0
- pandaserver/test/lsst/lsstSubmit.py +139 -0
- pandaserver/test/lsst/lsstSubmitMERGEtest.py +141 -0
- pandaserver/test/lsst/lsstSubmitPhosim332.py +53 -0
- pandaserver/test/lsst/prepare-client-tarball-from-bigpanda-server.sh +41 -0
- pandaserver/test/reassignJobs.py +17 -0
- pandaserver/test/reassignSite.py +82 -0
- pandaserver/test/reassignTask.py +117 -0
- pandaserver/test/reassignWaiting.py +39 -0
- pandaserver/test/recoverLostFiles.py +7 -0
- pandaserver/test/reloadInputDS.py +51 -0
- pandaserver/test/sendCommandToJob.py +26 -0
- pandaserver/test/setDebugMode.py +38 -0
- pandaserver/test/setPriority.py +28 -0
- pandaserver/test/testEvgen.py +60 -0
- pandaserver/test/testEvgen17.py +58 -0
- pandaserver/test/testG4sim.py +81 -0
- pandaserver/test/testG4sim17.py +90 -0
- pandaserver/test/testGlobalShares.py +131 -0
- pandaserver/test/testReco.py +103 -0
- pandaserver/test/testSimulReco14.py +98 -0
- pandaserver/test/testSiteMap.py +26 -0
- pandaserver/test/testUpdateWorkerPilotStatus.py +9 -0
- pandaserver/userinterface/Client.py +1686 -0
- pandaserver/userinterface/UserIF.py +1799 -0
- pandaserver/userinterface/__init__.py +0 -0
- pandaserver/workflow/__init__.py +0 -0
- pandaserver/workflow/data_handler_plugins/base_data_handler.py +48 -0
- pandaserver/workflow/data_handler_plugins/ddm_collection_data_handler.py +100 -0
- pandaserver/workflow/data_handler_plugins/panda_task_data_handler.py +140 -0
- pandaserver/workflow/pcwl_test.py +122 -0
- pandaserver/workflow/pcwl_utils.py +384 -0
- pandaserver/workflow/psnakemake_container.json +68 -0
- pandaserver/workflow/psnakemake_task.json +62 -0
- pandaserver/workflow/psnakemake_test.py +60 -0
- pandaserver/workflow/snakeparser/__init__.py +3 -0
- pandaserver/workflow/snakeparser/extensions.py +74 -0
- pandaserver/workflow/snakeparser/log.py +53 -0
- pandaserver/workflow/snakeparser/names.py +3 -0
- pandaserver/workflow/snakeparser/parser.py +332 -0
- pandaserver/workflow/snakeparser/utils.py +29 -0
- pandaserver/workflow/step_handler_plugins/base_step_handler.py +85 -0
- pandaserver/workflow/step_handler_plugins/panda_task_step_handler.py +280 -0
- pandaserver/workflow/workflow_base.py +531 -0
- pandaserver/workflow/workflow_core.py +1974 -0
- pandaserver/workflow/workflow_core_smoketest.py +388 -0
- pandaserver/workflow/workflow_parser.py +222 -0
- pandaserver/workflow/workflow_utils.py +1047 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from pandajedi.jediconfig import jedi_config
|
|
2
|
+
from pandajedi.jedicore import Interaction
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# interface to JediTaskBuffer
|
|
6
|
+
class JediTaskBufferInterface:
|
|
7
|
+
# constructor
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.interface = None
|
|
10
|
+
|
|
11
|
+
# setup interface
|
|
12
|
+
def setupInterface(self, max_size=None):
|
|
13
|
+
vo = "any"
|
|
14
|
+
maxSize = max_size if max_size is not None else jedi_config.db.nWorkers
|
|
15
|
+
moduleName = "pandajedi.jedicore.JediTaskBuffer"
|
|
16
|
+
className = "JediTaskBuffer"
|
|
17
|
+
self.interface = Interaction.CommandSendInterface(vo, maxSize, moduleName, className)
|
|
18
|
+
self.interface.initialize()
|
|
19
|
+
|
|
20
|
+
# method emulation
|
|
21
|
+
def __getattr__(self, attrName):
|
|
22
|
+
return getattr(self.interface, attrName)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if __name__ == "__main__":
|
|
26
|
+
|
|
27
|
+
def dummyClient(dif, stime):
|
|
28
|
+
print("client test")
|
|
29
|
+
import time
|
|
30
|
+
|
|
31
|
+
for i in range(3):
|
|
32
|
+
# time.sleep(i*stime)
|
|
33
|
+
try:
|
|
34
|
+
print(dif.getCloudList())
|
|
35
|
+
except Exception:
|
|
36
|
+
print("exp")
|
|
37
|
+
print("client done")
|
|
38
|
+
|
|
39
|
+
dif = JediTaskBufferInterface()
|
|
40
|
+
dif.setupInterface()
|
|
41
|
+
print("master test")
|
|
42
|
+
print(dif.getCloudList())
|
|
43
|
+
print("master done")
|
|
44
|
+
import multiprocessing
|
|
45
|
+
|
|
46
|
+
pList = []
|
|
47
|
+
for i in range(5):
|
|
48
|
+
p = multiprocessing.Process(target=dummyClient, args=(dif, i))
|
|
49
|
+
pList.append(p)
|
|
50
|
+
p.start()
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
4
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
5
|
+
|
|
6
|
+
from pandajedi.jediconfig import jedi_config
|
|
7
|
+
from pandaserver.userinterface import Client
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MsgWrapper:
|
|
11
|
+
def __init__(self, logger, token=None, lineLimit=500, monToken=None):
|
|
12
|
+
self.logger = logger
|
|
13
|
+
# use timestamp as token if undefined
|
|
14
|
+
if token is None:
|
|
15
|
+
self.token = f"<{naive_utcnow().isoformat('/')}>"
|
|
16
|
+
else:
|
|
17
|
+
self.token = token
|
|
18
|
+
# token for http logger
|
|
19
|
+
if monToken is None:
|
|
20
|
+
self.monToken = self.token
|
|
21
|
+
else:
|
|
22
|
+
self.monToken = monToken
|
|
23
|
+
# remove <> for django
|
|
24
|
+
try:
|
|
25
|
+
self.monToken = re.sub("<(?P<name>[^>]+)>", "\g<name>", self.monToken)
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
# message buffer
|
|
29
|
+
self.msgBuffer = []
|
|
30
|
+
self.bareMsg = []
|
|
31
|
+
self.lineLimit = lineLimit
|
|
32
|
+
self.message_slot = None
|
|
33
|
+
|
|
34
|
+
def keepMsg(self, msg):
|
|
35
|
+
# check if message slot is defined and available
|
|
36
|
+
if self.message_slot is not None:
|
|
37
|
+
if self.message_slot < 0:
|
|
38
|
+
return
|
|
39
|
+
if self.message_slot == 0:
|
|
40
|
+
msg = " ..."
|
|
41
|
+
self.message_slot -= 1
|
|
42
|
+
# keep max message depth
|
|
43
|
+
if len(self.msgBuffer) > self.lineLimit:
|
|
44
|
+
self.msgBuffer.pop(0)
|
|
45
|
+
self.bareMsg.pop(0)
|
|
46
|
+
timeNow = naive_utcnow()
|
|
47
|
+
self.msgBuffer.append(f"{timeNow.isoformat(' ')} : {msg}")
|
|
48
|
+
self.bareMsg.append(msg)
|
|
49
|
+
|
|
50
|
+
def set_message_slot(self, slot: int = 10):
|
|
51
|
+
self.message_slot = slot
|
|
52
|
+
|
|
53
|
+
def unset_message_slot(self):
|
|
54
|
+
self.message_slot = None
|
|
55
|
+
|
|
56
|
+
def info(self, msg):
|
|
57
|
+
msg = str(msg)
|
|
58
|
+
self.logger.info(self.token + " " + msg)
|
|
59
|
+
self.keepMsg(msg)
|
|
60
|
+
|
|
61
|
+
def debug(self, msg):
|
|
62
|
+
msg = str(msg)
|
|
63
|
+
self.logger.debug(self.token + " " + msg)
|
|
64
|
+
|
|
65
|
+
def error(self, msg):
|
|
66
|
+
msg = str(msg)
|
|
67
|
+
self.logger.error(self.token + " " + msg)
|
|
68
|
+
self.keepMsg(msg)
|
|
69
|
+
|
|
70
|
+
def warning(self, msg):
|
|
71
|
+
msg = str(msg)
|
|
72
|
+
self.logger.warning(self.token + " " + msg)
|
|
73
|
+
self.keepMsg(msg)
|
|
74
|
+
|
|
75
|
+
def dumpToString(self):
|
|
76
|
+
strMsg = ""
|
|
77
|
+
for msg in self.msgBuffer:
|
|
78
|
+
strMsg += msg
|
|
79
|
+
strMsg += "\n"
|
|
80
|
+
return strMsg
|
|
81
|
+
|
|
82
|
+
def uploadLog(self, id):
|
|
83
|
+
strMsg = self.dumpToString()
|
|
84
|
+
s, o = Client.uploadLog(strMsg, id)
|
|
85
|
+
if s != 0:
|
|
86
|
+
return f"failed to upload log with {s} {o}."
|
|
87
|
+
|
|
88
|
+
success = o["success"]
|
|
89
|
+
message = o["message"]
|
|
90
|
+
url = o["data"]
|
|
91
|
+
|
|
92
|
+
if success and url.startswith("http"):
|
|
93
|
+
return f"<a href=\"{url}\">log</a> : {'. '.join(self.bareMsg[-2:])}."
|
|
94
|
+
|
|
95
|
+
return message
|
|
96
|
+
|
|
97
|
+
# send message to logger
|
|
98
|
+
def sendMsg(self, message, msgType, msgLevel="info", escapeChar=False):
|
|
99
|
+
try:
|
|
100
|
+
# get logger
|
|
101
|
+
tmpPandaLogger = PandaLogger()
|
|
102
|
+
# lock HTTP handler
|
|
103
|
+
tmpPandaLogger.lock()
|
|
104
|
+
tmpPandaLogger.setParams({"Type": msgType})
|
|
105
|
+
# get logger
|
|
106
|
+
tmpLogger = tmpPandaLogger.getHttpLogger(jedi_config.master.loggername)
|
|
107
|
+
# escape special characters
|
|
108
|
+
if escapeChar:
|
|
109
|
+
message = message.replace("<", "<")
|
|
110
|
+
message = message.replace(">", ">")
|
|
111
|
+
# add message
|
|
112
|
+
message = self.monToken + " " + message
|
|
113
|
+
if msgLevel == "error":
|
|
114
|
+
tmpLogger.error(message)
|
|
115
|
+
elif msgLevel == "warning":
|
|
116
|
+
tmpLogger.warning(message)
|
|
117
|
+
elif msgLevel == "info":
|
|
118
|
+
tmpLogger.info(message)
|
|
119
|
+
else:
|
|
120
|
+
tmpLogger.debug(message)
|
|
121
|
+
# release HTTP handler
|
|
122
|
+
tmpPandaLogger.release()
|
|
123
|
+
except Exception:
|
|
124
|
+
pass
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import multiprocessing
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
6
|
+
|
|
7
|
+
from pandaserver.srvcore import CoreUtils
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# wrapper for multiprocessing.Process
|
|
11
|
+
class ProcessWrapper(multiprocessing.Process):
|
|
12
|
+
# constructor
|
|
13
|
+
def __init__(self, target, args):
|
|
14
|
+
multiprocessing.Process.__init__(self, target=self.wrappedMain)
|
|
15
|
+
self.target = target
|
|
16
|
+
self.args = args
|
|
17
|
+
|
|
18
|
+
# main
|
|
19
|
+
def wrappedMain(self):
|
|
20
|
+
while True:
|
|
21
|
+
proc = multiprocessing.Process(target=self.target, args=self.args)
|
|
22
|
+
proc.start()
|
|
23
|
+
pid = proc.pid
|
|
24
|
+
while True:
|
|
25
|
+
try:
|
|
26
|
+
proc.join(20)
|
|
27
|
+
if not CoreUtils.checkProcess(pid):
|
|
28
|
+
timeNow = naive_utcnow()
|
|
29
|
+
print(f"{str(timeNow)} {self.__class__.__name__}: INFO pid={pid} not exist")
|
|
30
|
+
break
|
|
31
|
+
except Exception:
|
|
32
|
+
timeNow = naive_utcnow()
|
|
33
|
+
errType, errValue = sys.exc_info()[:2]
|
|
34
|
+
print(f"{str(timeNow)} {self.__class__.__name__}: INFO failed to check pid={pid} with {errType} {errValue}")
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
class SiteCandidate(object):
|
|
2
|
+
def __init__(self, siteName, unifiedName=None):
|
|
3
|
+
# the site name
|
|
4
|
+
self.siteName = siteName
|
|
5
|
+
# unified name
|
|
6
|
+
self.unifiedName = unifiedName
|
|
7
|
+
# the weight for the brokerage
|
|
8
|
+
self.weight = 0
|
|
9
|
+
# the list of files copied from SE disk
|
|
10
|
+
self.localDiskFiles = set()
|
|
11
|
+
# the list of files copied from SE tape
|
|
12
|
+
self.localTapeFiles = set()
|
|
13
|
+
# the list of files cached in non-SE, e.g. on CVMFS
|
|
14
|
+
self.cacheFiles = set()
|
|
15
|
+
# the list of files read from SE using remote I/O
|
|
16
|
+
self.remoteFiles = set()
|
|
17
|
+
# the list of all files
|
|
18
|
+
self.allFiles = None
|
|
19
|
+
# remote access protocol
|
|
20
|
+
self.remoteProtocol = None
|
|
21
|
+
# remote source if any
|
|
22
|
+
self.remoteSource = None
|
|
23
|
+
# number of running job
|
|
24
|
+
self.nRunningJobs = None
|
|
25
|
+
# number of queued jobs
|
|
26
|
+
self.nQueuedJobs = None
|
|
27
|
+
# number of assigned jobs
|
|
28
|
+
self.nAssignedJobs = None
|
|
29
|
+
# cap on running jobs
|
|
30
|
+
self.nRunningJobsCap = None
|
|
31
|
+
# overridden attributes
|
|
32
|
+
self.overriddenAttrs = {}
|
|
33
|
+
|
|
34
|
+
# add local disk files
|
|
35
|
+
def add_local_disk_files(self, files):
|
|
36
|
+
self.localDiskFiles = self.localDiskFiles.union([f.fileID for f in files])
|
|
37
|
+
|
|
38
|
+
# add local tape files
|
|
39
|
+
def add_local_tape_files(self, files):
|
|
40
|
+
self.localTapeFiles = self.localTapeFiles.union([f.fileID for f in files])
|
|
41
|
+
|
|
42
|
+
# add cache files
|
|
43
|
+
def add_cache_files(self, files):
|
|
44
|
+
self.cacheFiles = self.cacheFiles.union([f.fileID for f in files])
|
|
45
|
+
|
|
46
|
+
# add remote files
|
|
47
|
+
def add_remote_files(self, files):
|
|
48
|
+
self.remoteFiles = self.remoteFiles.union([f.fileID for f in files])
|
|
49
|
+
|
|
50
|
+
# get locality of a file
|
|
51
|
+
def getFileLocality(self, fileSpec):
|
|
52
|
+
if fileSpec.fileID in self.localDiskFiles:
|
|
53
|
+
return "localdisk"
|
|
54
|
+
if fileSpec.fileID in self.localTapeFiles:
|
|
55
|
+
return "localtape"
|
|
56
|
+
if fileSpec.fileID in self.cacheFiles:
|
|
57
|
+
return "cache"
|
|
58
|
+
if fileSpec.fileID in self.remoteFiles:
|
|
59
|
+
return "remote"
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
# add available files
|
|
63
|
+
def addAvailableFiles(self, fileList):
|
|
64
|
+
if self.allFiles is None:
|
|
65
|
+
self.allFiles = set()
|
|
66
|
+
for tmpFileSpec in fileList:
|
|
67
|
+
self.allFiles.add(tmpFileSpec.fileID)
|
|
68
|
+
|
|
69
|
+
# check if file is available
|
|
70
|
+
def isAvailableFile(self, tmpFileSpec):
|
|
71
|
+
# N/A
|
|
72
|
+
if self.allFiles is None:
|
|
73
|
+
return True
|
|
74
|
+
return tmpFileSpec.fileID in self.allFiles
|
|
75
|
+
|
|
76
|
+
# check if still can accept jobs
|
|
77
|
+
def can_accept_jobs(self):
|
|
78
|
+
if self.nRunningJobsCap is None or self.nQueuedJobs is None:
|
|
79
|
+
return True
|
|
80
|
+
if self.nRunningJobsCap > self.nQueuedJobs:
|
|
81
|
+
return True
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
# override an attribute
|
|
85
|
+
def override_attribute(self, key, value):
|
|
86
|
+
self.overriddenAttrs[key] = value
|
|
87
|
+
|
|
88
|
+
# get an overridden attribute
|
|
89
|
+
def get_overridden_attribute(self, key):
|
|
90
|
+
return self.overriddenAttrs.get(key)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import multiprocessing
|
|
2
|
+
import sys
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# list with lock
|
|
8
|
+
class ListWithLock:
|
|
9
|
+
def __init__(self, dataList):
|
|
10
|
+
self.lock = threading.Lock()
|
|
11
|
+
self.dataList = dataList
|
|
12
|
+
self.dataIndex = 0
|
|
13
|
+
|
|
14
|
+
def __iter__(self):
|
|
15
|
+
return self
|
|
16
|
+
|
|
17
|
+
def __contains__(self, item):
|
|
18
|
+
self.lock.acquire()
|
|
19
|
+
ret = self.dataList.__contains__(item)
|
|
20
|
+
self.lock.release()
|
|
21
|
+
return ret
|
|
22
|
+
|
|
23
|
+
def __next__(self):
|
|
24
|
+
if self.dataIndex >= len(self.dataList):
|
|
25
|
+
self.dataIndex = 0
|
|
26
|
+
raise StopIteration
|
|
27
|
+
val = self.dataList[self.dataIndex]
|
|
28
|
+
self.dataIndex += 1
|
|
29
|
+
return val
|
|
30
|
+
|
|
31
|
+
def next(self):
|
|
32
|
+
return self.__next__()
|
|
33
|
+
|
|
34
|
+
def append(self, item):
|
|
35
|
+
self.lock.acquire()
|
|
36
|
+
appended = False
|
|
37
|
+
if item not in self.dataList:
|
|
38
|
+
self.dataList.append(item)
|
|
39
|
+
appended = True
|
|
40
|
+
self.lock.release()
|
|
41
|
+
return appended
|
|
42
|
+
|
|
43
|
+
def get(self, num):
|
|
44
|
+
self.lock.acquire()
|
|
45
|
+
retList = self.dataList[self.dataIndex : self.dataIndex + num]
|
|
46
|
+
self.dataIndex += len(retList)
|
|
47
|
+
self.lock.release()
|
|
48
|
+
return retList
|
|
49
|
+
|
|
50
|
+
def stat(self):
|
|
51
|
+
self.lock.acquire()
|
|
52
|
+
total = len(self.dataList)
|
|
53
|
+
nIndx = self.dataIndex
|
|
54
|
+
self.lock.release()
|
|
55
|
+
return total, nIndx
|
|
56
|
+
|
|
57
|
+
def __len__(self):
|
|
58
|
+
self.lock.acquire()
|
|
59
|
+
ret = len(self.dataList)
|
|
60
|
+
self.lock.release()
|
|
61
|
+
return ret
|
|
62
|
+
|
|
63
|
+
def dump(self):
|
|
64
|
+
self.lock.acquire()
|
|
65
|
+
if len(self.dataList) > self.dataIndex:
|
|
66
|
+
ret = ",".join(map(str, self.dataList[self.dataIndex :]))
|
|
67
|
+
else:
|
|
68
|
+
ret = "None"
|
|
69
|
+
self.lock.release()
|
|
70
|
+
return ret
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# map with lock
|
|
74
|
+
class MapWithLock:
|
|
75
|
+
def __init__(self, dataMap=None):
|
|
76
|
+
self.lock = threading.Lock()
|
|
77
|
+
if dataMap is None:
|
|
78
|
+
dataMap = {}
|
|
79
|
+
self.dataMap = dataMap
|
|
80
|
+
|
|
81
|
+
def __getitem__(self, item):
|
|
82
|
+
ret = self.dataMap.__getitem__(item)
|
|
83
|
+
return ret
|
|
84
|
+
|
|
85
|
+
def __setitem__(self, item, value):
|
|
86
|
+
self.dataMap.__setitem__(item, value)
|
|
87
|
+
|
|
88
|
+
def __contains__(self, item):
|
|
89
|
+
ret = self.dataMap.__contains__(item)
|
|
90
|
+
return ret
|
|
91
|
+
|
|
92
|
+
def acquire(self):
|
|
93
|
+
self.lock.acquire()
|
|
94
|
+
|
|
95
|
+
def release(self):
|
|
96
|
+
self.lock.release()
|
|
97
|
+
|
|
98
|
+
def add(self, item, value):
|
|
99
|
+
if item not in self.dataMap:
|
|
100
|
+
self.dataMap[item] = 0
|
|
101
|
+
self.dataMap[item] += value
|
|
102
|
+
|
|
103
|
+
def get(self, item):
|
|
104
|
+
if item not in self.dataMap:
|
|
105
|
+
return 0
|
|
106
|
+
return self.dataMap[item]
|
|
107
|
+
|
|
108
|
+
def items(self):
|
|
109
|
+
return self.dataMap.items()
|
|
110
|
+
|
|
111
|
+
def iteritems(self):
|
|
112
|
+
return self.items()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# thread pool
|
|
116
|
+
class ThreadPool:
|
|
117
|
+
def __init__(self):
|
|
118
|
+
self.lock = threading.Lock()
|
|
119
|
+
self.list = []
|
|
120
|
+
|
|
121
|
+
# add thread
|
|
122
|
+
def add(self, obj):
|
|
123
|
+
self.lock.acquire()
|
|
124
|
+
self.list.append(obj)
|
|
125
|
+
self.lock.release()
|
|
126
|
+
|
|
127
|
+
# remove thread
|
|
128
|
+
def remove(self, obj):
|
|
129
|
+
self.lock.acquire()
|
|
130
|
+
try:
|
|
131
|
+
self.list.remove(obj)
|
|
132
|
+
except Exception:
|
|
133
|
+
pass
|
|
134
|
+
self.lock.release()
|
|
135
|
+
|
|
136
|
+
# join
|
|
137
|
+
def join(self, timeOut=None):
|
|
138
|
+
thrlist = tuple(self.list)
|
|
139
|
+
for thr in thrlist:
|
|
140
|
+
try:
|
|
141
|
+
thr.join(timeOut)
|
|
142
|
+
if thr.is_alive():
|
|
143
|
+
break
|
|
144
|
+
except Exception:
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
# remove inactive threads
|
|
148
|
+
def clean(self):
|
|
149
|
+
thrlist = tuple(self.list)
|
|
150
|
+
for thr in thrlist:
|
|
151
|
+
if not thr.is_alive():
|
|
152
|
+
self.remove(thr)
|
|
153
|
+
|
|
154
|
+
# dump contents
|
|
155
|
+
def dump(self):
|
|
156
|
+
thrlist = tuple(self.list)
|
|
157
|
+
nActv = 0
|
|
158
|
+
for thr in thrlist:
|
|
159
|
+
if thr.is_alive():
|
|
160
|
+
nActv += 1
|
|
161
|
+
return f"nActive={nActv}"
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# thread class working with semaphore and thread pool
|
|
165
|
+
class WorkerThread(threading.Thread):
|
|
166
|
+
# constructor
|
|
167
|
+
def __init__(self, workerSemaphore, threadPool, logger):
|
|
168
|
+
threading.Thread.__init__(self)
|
|
169
|
+
self.workerSemaphore = workerSemaphore
|
|
170
|
+
self.threadPool = threadPool
|
|
171
|
+
if self.threadPool is not None:
|
|
172
|
+
self.threadPool.add(self)
|
|
173
|
+
self.logger = logger
|
|
174
|
+
|
|
175
|
+
# main loop
|
|
176
|
+
def run(self):
|
|
177
|
+
# get slot
|
|
178
|
+
if self.workerSemaphore is not None:
|
|
179
|
+
self.workerSemaphore.acquire()
|
|
180
|
+
# execute real work
|
|
181
|
+
try:
|
|
182
|
+
self.runImpl()
|
|
183
|
+
except Exception:
|
|
184
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
185
|
+
self.logger.error(f"{self.__class__.__name__} crashed in WorkerThread.run() with {errtype.__name__}:{errvalue}")
|
|
186
|
+
# remove self from thread pool
|
|
187
|
+
if self.threadPool is not None:
|
|
188
|
+
self.threadPool.remove(self)
|
|
189
|
+
# release slot
|
|
190
|
+
if self.workerSemaphore is not None:
|
|
191
|
+
self.workerSemaphore.release()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# thread class to cleanup zombi processes
|
|
195
|
+
class ZombieCleaner(threading.Thread):
|
|
196
|
+
# constructor
|
|
197
|
+
def __init__(self, interval=20):
|
|
198
|
+
threading.Thread.__init__(self)
|
|
199
|
+
self.interval = interval
|
|
200
|
+
|
|
201
|
+
# main loop
|
|
202
|
+
def run(self):
|
|
203
|
+
while True:
|
|
204
|
+
x = multiprocessing.active_children()
|
|
205
|
+
time.sleep(self.interval)
|
|
File without changes
|