Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
taosdata
TDengine
提交
fb9376be
T
TDengine
项目概览
taosdata
/
TDengine
1 年多 前同步成功
通知
1185
Star
22016
Fork
4786
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
1
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
T
TDengine
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
1
Issue
1
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
提交
fb9376be
编写于
10月 20, 2020
作者:
S
Steven Li
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Introduced TdeInstance concept into crash_gen tool, ready to run clusters next
上级
6fea1f43
变更
1
隐藏空白更改
内联
并排
Showing
1 changed file
with
220 addition
and
148 deletion
+220
-148
tests/pytest/crash_gen/crash_gen.py
tests/pytest/crash_gen/crash_gen.py
+220
-148
未找到文件。
tests/pytest/crash_gen/crash_gen.py
浏览文件 @
fb9376be
...
...
@@ -44,6 +44,7 @@ import traceback
import
resource
from
guppy
import
hpy
import
gc
import
subprocess
try
:
import
psutil
...
...
@@ -59,12 +60,13 @@ if sys.version_info[0] < 3:
# Command-line/Environment Configurations, will set a bit later
# ConfigNameSpace = argparse.Namespace
gConfig
=
argparse
.
Namespace
()
# Dummy value, will be replaced later
gSvcMgr
=
None
# TODO: refactor this hack, use dep injection
logger
=
None
# type: Logger
gConfig
:
argparse
.
Namespace
gSvcMgr
:
ServiceManager
# TODO: refactor this hack, use dep injection
logger
:
logging
.
Logger
gContainer
:
Container
def
runThread
(
wt
:
WorkerThread
):
wt
.
run
()
#
def runThread(wt: WorkerThread):
#
wt.run()
class
CrashGenError
(
Exception
):
def
__init__
(
self
,
msg
=
None
,
errno
=
None
):
...
...
@@ -74,7 +76,6 @@ class CrashGenError(Exception):
def
__str__
(
self
):
return
self
.
msg
class
WorkerThread
:
def
__init__
(
self
,
pool
:
ThreadPool
,
tid
,
tc
:
ThreadCoordinator
,
# te: TaskExecutor,
...
...
@@ -84,7 +85,8 @@ class WorkerThread:
self
.
_tid
=
tid
self
.
_tc
=
tc
# type: ThreadCoordinator
# self.threadIdent = threading.get_ident()
self
.
_thread
=
threading
.
Thread
(
target
=
runThread
,
args
=
(
self
,))
# self._thread = threading.Thread(target=runThread, args=(self,))
self
.
_thread
=
threading
.
Thread
(
target
=
self
.
run
)
self
.
_stepGate
=
threading
.
Event
()
# Let us have a DB connection of our own
...
...
@@ -253,7 +255,7 @@ class WorkerThread:
class
ThreadCoordinator
:
WORKER_THREAD_TIMEOUT
=
6
0
# one minute
WORKER_THREAD_TIMEOUT
=
18
0
# one minute
def
__init__
(
self
,
pool
:
ThreadPool
,
dbManager
:
DbManager
):
self
.
_curStep
=
-
1
# first step is 0
...
...
@@ -882,20 +884,15 @@ class MyTDSql:
raise
return
self
.
affectedRows
class
TdeInstance
():
"""
A class to capture the *static* information of a TDengine instance,
including the location of the various files/directories, and basica
configuration.
"""
class
DbConnNative
(
DbConn
):
# Class variables
_lock
=
threading
.
Lock
()
_connInfoDisplayed
=
False
totalConnections
=
0
# Not private
def
__init__
(
self
):
super
().
__init__
()
self
.
_type
=
self
.
TYPE_NATIVE
self
.
_conn
=
None
# self._cursor = None
def
getBuildPath
(
self
):
@
classmethod
def
_getBuildPath
(
cls
):
selfPath
=
os
.
path
.
dirname
(
os
.
path
.
realpath
(
__file__
))
if
(
"community"
in
selfPath
):
projPath
=
selfPath
[:
selfPath
.
find
(
"communit"
)]
...
...
@@ -914,10 +911,118 @@ class DbConnNative(DbConn):
.
format
(
selfPath
,
projPath
))
return
buildPath
def
__init__
(
self
,
subdir
=
'test'
):
self
.
_buildDir
=
self
.
_getBuildPath
()
self
.
_subdir
=
'/'
+
subdir
# TODO: tolerate "/"
def
__repr__
(
self
):
return
"[TdeInstance: {}, subdir={}]"
.
format
(
self
.
_buildDir
,
self
.
_subdir
)
def
generateCfgFile
(
self
):
# buildPath = self.getBuildPath()
# taosdPath = self._buildPath + "/build/bin/taosd"
cfgDir
=
self
.
getCfgDir
()
cfgFile
=
cfgDir
+
"/taos.cfg"
# TODO: inquire if this is fixed
if
os
.
path
.
exists
(
cfgFile
):
if
os
.
path
.
isfile
(
cfgFile
):
logger
.
warning
(
"Config file exists already, skip creation: {}"
.
format
(
cfgFile
))
return
# cfg file already exists, nothing to do
else
:
raise
CrashGenError
(
"Invalid config file: {}"
.
format
(
cfgFile
))
# Now that the cfg file doesn't exist
if
os
.
path
.
exists
(
cfgDir
):
if
not
os
.
path
.
isdir
(
cfgDir
):
raise
CrashGenError
(
"Invalid config dir: {}"
.
format
(
cfgDir
))
# else: good path
else
:
os
.
makedirs
(
cfgDir
,
exist_ok
=
True
)
# like "mkdir -p"
# Now we have a good cfg dir
cfgValues
=
{
'runDir'
:
self
.
getRunDir
(),
'ip'
:
'127.0.0.1'
,
# TODO: change to a network addressable ip
'port'
:
6030
,
}
cfgTemplate
=
"""
dataDir {runDir}/data
logDir {runDir}/log
charset UTF-8
firstEp {ip}:{port}
fqdn {ip}
serverPort {port}
# was all 135 below
dDebugFlag 135
cDebugFlag 135
rpcDebugFlag 135
qDebugFlag 135
# httpDebugFlag 143
# asyncLog 0
# tables 10
maxtablesPerVnode 10
rpcMaxTime 101
# cache 2
keep 36500
# walLevel 2
walLevel 1
#
# maxConnections 100
"""
cfgContent
=
cfgTemplate
.
format_map
(
cfgValues
)
f
=
open
(
cfgFile
,
"w"
)
f
.
write
(
cfgContent
)
f
.
close
()
def
rotateLogs
(
self
):
logPath
=
self
.
getLogDir
()
# ref: https://stackoverflow.com/questions/1995373/deleting-all-files-in-a-directory-with-python/1995397
if
os
.
path
.
exists
(
logPath
):
logPathSaved
=
logPath
+
"_"
+
time
.
strftime
(
'%Y-%m-%d-%H-%M-%S'
)
logger
.
info
(
"Saving old log files to: {}"
.
format
(
logPathSaved
))
os
.
rename
(
logPath
,
logPathSaved
)
# os.mkdir(logPath) # recreate, no need actually, TDengine will auto-create with proper perms
def
getExecFile
(
self
):
# .../taosd
return
self
.
_buildDir
+
"/build/bin/taosd"
def
getRunDir
(
self
):
# TODO: rename to "root dir" ?!
return
self
.
_buildDir
+
self
.
_subdir
def
getCfgDir
(
self
):
# path, not file
return
self
.
getRunDir
()
+
"/cfg"
def
getLogDir
(
self
):
return
self
.
getRunDir
()
+
"/log"
def
getHostAddr
(
self
):
return
"127.0.0.1"
def
getServiceCommand
(
self
):
# to start the instance
return
[
self
.
getExecFile
(),
'-c'
,
self
.
getCfgDir
()]
# used in subproce.Popen()
class
DbConnNative
(
DbConn
):
# Class variables
_lock
=
threading
.
Lock
()
_connInfoDisplayed
=
False
totalConnections
=
0
# Not private
def
__init__
(
self
):
super
().
__init__
()
self
.
_type
=
self
.
TYPE_NATIVE
self
.
_conn
=
None
# self._cursor = None
def
openByType
(
self
):
# Open connection
cfgPath
=
self
.
getBuildPath
()
+
"/test/cfg"
hostAddr
=
"127.0.0.1"
global
gContainer
tdeInstance
=
gContainer
.
defTdeInstance
# set up in ClientManager, type: TdeInstance
# cfgPath = self.getBuildPath() + "/test/cfg"
cfgPath
=
tdeInstance
.
getCfgDir
()
hostAddr
=
tdeInstance
.
getHostAddr
()
cls
=
self
.
__class__
# Get the class, to access class variables
with
cls
.
_lock
:
# force single threading for opening DB connections. # TODO: whaaat??!!!
...
...
@@ -1662,7 +1767,7 @@ class Task():
0x503
,
0x510
,
# vnode not in ready state
0x14
,
# db not ready, errno changed
0x600
,
0x600
,
# Invalid table ID, why?
1000
# REST catch-all error
]:
return
True
# These are the ALWAYS-ACCEPTABLE ones
...
...
@@ -1824,7 +1929,7 @@ class ExecutionStats:
"FAILED (reason: {})"
.
format
(
self
.
_failureReason
)
if
self
.
_failed
else
"SUCCEEDED"
))
logger
.
info
(
"| Task Execution Times (success/total):"
)
execTimesAny
=
0
execTimesAny
=
0
.001
# avoid div by zero
for
k
,
n
in
self
.
_execTimes
.
items
():
execTimesAny
+=
n
[
0
]
errStr
=
None
...
...
@@ -2343,7 +2448,9 @@ class MyLoggingAdapter(logging.LoggerAdapter):
# return '[%s] %s' % (self.extra['connid'], msg), kwargs
class
SvcManager
:
class
ServiceManager
:
PAUSE_BETWEEN_IPC_CHECK
=
1.2
# seconds between checks on STDOUT of sub process
def
__init__
(
self
):
print
(
"Starting TDengine Service Manager"
)
# signal.signal(signal.SIGTERM, self.sigIntHandler) # Moved to MainExec
...
...
@@ -2384,10 +2491,8 @@ class SvcManager:
self
.
inSigHandler
=
True
choice
=
self
.
_doMenu
()
if
choice
==
"1"
:
# TODO: can the sub-process be blocked due to us not reading from
# queue?
self
.
sigHandlerResume
()
if
choice
==
"1"
:
self
.
sigHandlerResume
()
# TODO: can the sub-process be blocked due to us not reading from queue?
elif
choice
==
"2"
:
self
.
stopTaosService
()
elif
choice
==
"3"
:
# Restart
...
...
@@ -2398,20 +2503,20 @@ class SvcManager:
self
.
inSigHandler
=
False
def
sigIntHandler
(
self
,
signalNumber
,
frame
):
print
(
"S
vc
Manager: INT Signal Handler starting..."
)
print
(
"S
ervice
Manager: INT Signal Handler starting..."
)
if
self
.
inSigHandler
:
print
(
"Ignoring repeated SIG_INT..."
)
return
self
.
inSigHandler
=
True
self
.
stopTaosService
()
print
(
"S
vc
Manager: INT Signal Handler returning..."
)
print
(
"S
ervice
Manager: INT Signal Handler returning..."
)
self
.
inSigHandler
=
False
def
sigHandlerResume
(
self
):
print
(
"Resuming TDengine service manager
thread
(main thread)...
\n\n
"
)
print
(
"Resuming TDengine service manager (main thread)...
\n\n
"
)
def
_
checkServiceManagerThread
(
self
):
def
_
updateThreadStatus
(
self
):
if
self
.
svcMgrThread
:
# valid svc mgr thread
if
self
.
svcMgrThread
.
isStopped
():
# done?
self
.
svcMgrThread
.
procIpcBatch
()
# one last time. TODO: appropriate?
...
...
@@ -2419,14 +2524,13 @@ class SvcManager:
def
_procIpcAll
(
self
):
while
self
.
isRunning
()
or
self
.
isRestarting
()
:
# for as long as the svc mgr thread is still here
if
self
.
isRunning
():
if
self
.
isRunning
():
self
.
svcMgrThread
.
procIpcBatch
()
# regular processing,
self
.
_
checkServiceManagerThread
()
self
.
_
updateThreadStatus
()
elif
self
.
isRetarting
():
print
(
"Service restarting..."
)
time
.
sleep
(
0.5
)
# pause, before next round
print
(
"Service Manager Thread (with subprocess) has ended, main thread now exiting..."
)
time
.
sleep
(
self
.
PAUSE_BETWEEN_IPC_CHECK
)
# pause, before next round
print
(
"Service Manager Thread (with subprocess) ended, main thread exiting..."
)
def
startTaosService
(
self
):
with
self
.
_lock
:
...
...
@@ -2440,7 +2544,6 @@ class SvcManager:
time
.
sleep
(
2.0
)
proc
.
kill
()
# print("Process: {}".format(proc.name()))
self
.
svcMgrThread
=
ServiceManagerThread
()
# create the object
print
(
"Attempting to start TAOS service started, printing out output..."
)
...
...
@@ -2491,10 +2594,17 @@ class SvcManager:
return
self
.
_isRestarting
class
ServiceManagerThread
:
"""
A class representing a dedicated thread which manages the "sub process"
of the TDengine service, interacting with its STDOUT/ERR.
It takes a TdeInstance parameter at creation time, or create a default
"""
MAX_QUEUE_SIZE
=
10000
def
__init__
(
self
):
def
__init__
(
self
,
tInst
:
TdeInstance
=
None
):
self
.
_tdeSubProcess
=
None
# type: TdeSubProcess
self
.
_tInst
=
tInst
or
TdeInstance
()
# Need an instance
self
.
_thread
=
None
self
.
_status
=
None
...
...
@@ -2521,7 +2631,7 @@ class ServiceManagerThread:
self
.
_status
=
MainExec
.
STATUS_STARTING
self
.
_tdeSubProcess
=
TdeSubProcess
()
self
.
_tdeSubProcess
=
TdeSubProcess
(
self
.
_tInst
)
self
.
_tdeSubProcess
.
start
()
self
.
_ipcQueue
=
Queue
()
...
...
@@ -2681,8 +2791,19 @@ class ServiceManagerThread:
class
TdeSubProcess
:
def
__init__
(
self
):
"""
A class to to represent the actual sub process that is the run-time
of a TDengine instance.
It takes a TdeInstance object as its parameter, with the rationale being
"a sub process runs an instance".
"""
def
__init__
(
self
,
tInst
:
TdeInstance
):
self
.
subProcess
=
None
if
tInst
is
None
:
raise
CrashGenError
(
"Empty instance not allowed in TdeSubProcess"
)
self
.
_tInst
=
tInst
# Default create at ServiceManagerThread
def
getStdOut
(
self
):
return
self
.
subProcess
.
stdout
...
...
@@ -2696,50 +2817,39 @@ class TdeSubProcess:
def
getPid
(
self
):
return
self
.
subProcess
.
pid
def
getBuildPath
(
self
):
selfPath
=
os
.
path
.
dirname
(
os
.
path
.
realpath
(
__file__
))
if
(
"community"
in
selfPath
):
projPath
=
selfPath
[:
selfPath
.
find
(
"communit"
)]
else
:
projPath
=
selfPath
[:
selfPath
.
find
(
"tests"
)]
# Repalced by TdeInstance class
# def getBuildPath(self):
# selfPath = os.path.dirname(os.path.realpath(__file__))
# if ("community" in selfPath):
# projPath = selfPath[:selfPath.find("communit")]
# else:
# projPath = selfPath[:selfPath.find("tests")]
for
root
,
dirs
,
files
in
os
.
walk
(
projPath
):
if
(
"taosd"
in
files
):
rootRealPath
=
os
.
path
.
dirname
(
os
.
path
.
realpath
(
root
))
if
(
"packaging"
not
in
rootRealPath
):
buildPath
=
root
[:
len
(
root
)
-
len
(
"/build/bin"
)]
break
return
buildPath
#
for root, dirs, files in os.walk(projPath):
#
if ("taosd" in files):
#
rootRealPath = os.path.dirname(os.path.realpath(root))
#
if ("packaging" not in rootRealPath):
#
buildPath = root[:len(root) - len("/build/bin")]
#
break
#
return buildPath
def
start
(
self
):
ON_POSIX
=
'posix'
in
sys
.
builtin_module_names
taosdPath
=
self
.
getBuildPath
()
+
"/build/bin/taosd"
cfgPath
=
self
.
getBuildPath
()
+
"/test/cfg"
# Delete the log files
logPath
=
self
.
getBuildPath
()
+
"/test/log"
# ref: https://stackoverflow.com/questions/1995373/deleting-all-files-in-a-directory-with-python/1995397
# filelist = [ f for f in os.listdir(logPath) ] # if f.endswith(".bak") ]
# for f in filelist:
# filePath = os.path.join(logPath, f)
# print("Removing log file: {}".format(filePath))
# os.remove(filePath)
if
os
.
path
.
exists
(
logPath
):
logPathSaved
=
logPath
+
"_"
+
time
.
strftime
(
'%Y-%m-%d-%H-%M-%S'
)
logger
.
info
(
"Saving old log files to: {}"
.
format
(
logPathSaved
))
os
.
rename
(
logPath
,
logPathSaved
)
# os.mkdir(logPath) # recreate, no need actually, TDengine will auto-create with proper perms
svcCmd
=
[
taosdPath
,
'-c'
,
cfgPath
]
# svcCmdSingle = "{} -c {}".format(taosdPath, cfgPath)
# svcCmd = ['vmstat', '1']
# Sanity check
if
self
.
subProcess
:
# already there
raise
RuntimeError
(
"Corrupt process state"
)
# print("Starting service: {}".format(svcCmd))
# global gContainer
# tInst = gContainer.defTdeInstance = TdeInstance('test3') # creae the instance
self
.
_tInst
.
generateCfgFile
()
# service side generates config file, client does not
self
.
_tInst
.
rotateLogs
()
print
(
"Starting TDengine instance: {}"
.
format
(
self
.
_tInst
))
self
.
subProcess
=
subprocess
.
Popen
(
svcCmd
,
shell
=
False
,
self
.
_tInst
.
getServiceCommand
(),
shell
=
False
,
# svcCmdSingle, shell=True, # capture core dump?
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
PIPE
,
...
...
@@ -2898,10 +3008,15 @@ class ClientManager:
# self._printLastNumbers()
global
gConfig
# Prepare Tde Instance
global
gContainer
tInst
=
gContainer
.
defTdeInstance
=
TdeInstance
()
# "subdir to hold the instance"
dbManager
=
DbManager
()
# Regular function
thPool
=
ThreadPool
(
gConfig
.
num_threads
,
gConfig
.
max_steps
)
self
.
tc
=
ThreadCoordinator
(
thPool
,
dbManager
)
print
(
"Starting client instance to: {}"
.
format
(
tInst
))
self
.
tc
.
run
()
# print("exec stats: {}".format(self.tc.getExecStats()))
# print("TC failed = {}".format(self.tc.isFailed()))
...
...
@@ -2936,9 +3051,6 @@ class ClientManager:
# self.tc.getDbManager().cleanUp() # clean up first, so we can show ZERO db connections
self
.
tc
.
printStats
()
class
MainExec
:
STATUS_STARTING
=
1
STATUS_RUNNING
=
2
...
...
@@ -2968,7 +3080,7 @@ class MainExec:
def
runClient
(
self
):
global
gSvcMgr
if
gConfig
.
auto_start_service
:
self
.
_svcMgr
=
S
vc
Manager
()
self
.
_svcMgr
=
S
ervice
Manager
()
gSvcMgr
=
self
.
_svcMgr
# hack alert
self
.
_svcMgr
.
startTaosService
()
# we start, don't run
...
...
@@ -2983,55 +3095,13 @@ class MainExec:
def
runService
(
self
):
global
gSvcMgr
self
.
_svcMgr
=
S
vc
Manager
()
self
.
_svcMgr
=
S
ervice
Manager
()
gSvcMgr
=
self
.
_svcMgr
# save it in a global variable TODO: hack alert
self
.
_svcMgr
.
run
()
# run to some end state
self
.
_svcMgr
=
None
gSvcMgr
=
None
def
runTemp
(
self
):
# for debugging purposes
# # Hack to exercise reading from disk, imcreasing coverage. TODO: fix
# dbc = dbState.getDbConn()
# sTbName = dbState.getFixedSuperTableName()
# dbc.execute("create database if not exists db")
# if not dbState.getState().equals(StateEmpty()):
# dbc.execute("use db")
# rTables = None
# try: # the super table may not exist
# sql = "select TBNAME from db.{}".format(sTbName)
# logger.info("Finding out tables in super table: {}".format(sql))
# dbc.query(sql) # TODO: analyze result set later
# logger.info("Fetching result")
# rTables = dbc.getQueryResult()
# logger.info("Result: {}".format(rTables))
# except taos.error.ProgrammingError as err:
# logger.info("Initial Super table OPS error: {}".format(err))
# # sys.exit()
# if ( not rTables == None):
# # print("rTables[0] = {}, type = {}".format(rTables[0], type(rTables[0])))
# try:
# for rTbName in rTables : # regular tables
# ds = dbState
# logger.info("Inserting into table: {}".format(rTbName[0]))
# sql = "insert into db.{} values ('{}', {});".format(
# rTbName[0],
# ds.getNextTick(), ds.getNextInt())
# dbc.execute(sql)
# for rTbName in rTables : # regular tables
# dbc.query("select * from db.{}".format(rTbName[0])) # TODO: check success failure
# logger.info("Initial READING operation is successful")
# except taos.error.ProgrammingError as err:
# logger.info("Initial WRITE/READ error: {}".format(err))
# Sandbox testing code
# dbc = dbState.getDbConn()
# while True:
# rows = dbc.query("show databases")
# print("Rows: {}, time={}".format(rows, time.time()))
return
def
main
():
...
...
@@ -3045,28 +3115,7 @@ def main():
1. You build TDengine in the top level ./build directory, as described in offical docs
2. You run the server there before this script: ./build/bin/taosd -c test/cfg
'''
))
# parser.add_argument('-a', '--auto-start-service', action='store_true',
# help='Automatically start/stop the TDengine service (default: false)')
# parser.add_argument('-c', '--connector-type', action='store', default='native', type=str,
# help='Connector type to use: native, rest, or mixed (default: 10)')
# parser.add_argument('-d', '--debug', action='store_true',
# help='Turn on DEBUG mode for more logging (default: false)')
# parser.add_argument('-e', '--run-tdengine', action='store_true',
# help='Run TDengine service in foreground (default: false)')
# parser.add_argument('-l', '--larger-data', action='store_true',
# help='Write larger amount of data during write operations (default: false)')
# parser.add_argument('-p', '--per-thread-db-connection', action='store_true',
# help='Use a single shared db connection (default: false)')
# parser.add_argument('-r', '--record-ops', action='store_true',
# help='Use a pair of always-fsynced fils to record operations performing + performed, for power-off tests (default: false)')
# parser.add_argument('-s', '--max-steps', action='store', default=1000, type=int,
# help='Maximum number of steps to run (default: 100)')
# parser.add_argument('-t', '--num-threads', action='store', default=5, type=int,
# help='Number of threads to run (default: 10)')
# parser.add_argument('-x', '--continue-on-exception', action='store_true',
# help='Continue execution after encountering unexpected/disallowed errors/exceptions (default: false)')
'''
))
parser
.
add_argument
(
'-a'
,
...
...
@@ -3171,8 +3220,31 @@ def main():
else
:
return
mExec
.
runClient
()
class
Container
():
_propertyList
=
{
'defTdeInstance'
}
def
__init__
(
self
):
self
.
_cargo
=
{}
# No cargo at the beginning
def
_verifyValidProperty
(
self
,
name
):
if
not
name
in
self
.
_propertyList
:
raise
CrashGenError
(
"Invalid container property: {}"
.
format
(
name
))
# Called for an attribute, when other mechanisms fail (compare to __getattribute__)
def
__getattr__
(
self
,
name
):
self
.
_verifyValidProperty
(
name
)
return
self
.
_cargo
[
name
]
# just a simple lookup
def
__setattr__
(
self
,
name
,
value
):
if
name
==
'_cargo'
:
# reserved vars
super
().
__setattr__
(
name
,
value
)
return
self
.
_verifyValidProperty
(
name
)
self
.
_cargo
[
name
]
=
value
if
__name__
==
"__main__"
:
gContainer
=
Container
()
# micky-mouse DI
exitCode
=
main
()
# print("Exiting with code: {}".format(exitCode))
sys
.
exit
(
exitCode
)
sys
.
exit
(
exitCode
)
\ No newline at end of file
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录