Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
taosdata
TDengine
提交
7467a54a
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看板
未验证
提交
7467a54a
编写于
10月 17, 2022
作者:
L
Liu Jicong
提交者:
GitHub
10月 17, 2022
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #17396 from taosdata/fix/client_mem_leak
fix: client mem leak
上级
08397dc6
d2fc6215
变更
5
隐藏空白更改
内联
并排
Showing
5 changed file
with
217 addition
and
11 deletion
+217
-11
source/client/src/clientTmq.c
source/client/src/clientTmq.c
+3
-3
source/dnode/vnode/src/inc/tq.h
source/dnode/vnode/src/inc/tq.h
+1
-1
source/dnode/vnode/src/tq/tq.c
source/dnode/vnode/src/tq/tq.c
+6
-6
source/dnode/vnode/src/tq/tqSink.c
source/dnode/vnode/src/tq/tqSink.c
+206
-0
tests/system-test/7-tmq/tmqAutoCreateTbl.py
tests/system-test/7-tmq/tmqAutoCreateTbl.py
+1
-1
未找到文件。
source/client/src/clientTmq.c
浏览文件 @
7467a54a
...
@@ -709,6 +709,7 @@ void tmqSendHbReq(void* param, void* tmrId) {
...
@@ -709,6 +709,7 @@ void tmqSendHbReq(void* param, void* tmrId) {
int64_t
refId
=
*
(
int64_t
*
)
param
;
int64_t
refId
=
*
(
int64_t
*
)
param
;
tmq_t
*
tmq
=
taosAcquireRef
(
tmqMgmt
.
rsetId
,
refId
);
tmq_t
*
tmq
=
taosAcquireRef
(
tmqMgmt
.
rsetId
,
refId
);
if
(
tmq
==
NULL
)
{
if
(
tmq
==
NULL
)
{
taosMemoryFree
(
param
);
return
;
return
;
}
}
int64_t
consumerId
=
tmq
->
consumerId
;
int64_t
consumerId
=
tmq
->
consumerId
;
...
@@ -939,10 +940,9 @@ tmq_t* tmq_consumer_new(tmq_conf_t* conf, char* errstr, int32_t errstrLen) {
...
@@ -939,10 +940,9 @@ tmq_t* tmq_consumer_new(tmq_conf_t* conf, char* errstr, int32_t errstrLen) {
return
NULL
;
return
NULL
;
}
}
int64_t
*
pRefId
=
taosMemoryMalloc
(
sizeof
(
int64_t
));
*
pRefId
=
pTmq
->
refId
;
if
(
pTmq
->
hbBgEnable
)
{
if
(
pTmq
->
hbBgEnable
)
{
int64_t
*
pRefId
=
taosMemoryMalloc
(
sizeof
(
int64_t
));
*
pRefId
=
pTmq
->
refId
;
pTmq
->
hbLiveTimer
=
taosTmrStart
(
tmqSendHbReq
,
1000
,
pRefId
,
tmqMgmt
.
timer
);
pTmq
->
hbLiveTimer
=
taosTmrStart
(
tmqSendHbReq
,
1000
,
pRefId
,
tmqMgmt
.
timer
);
}
}
...
...
source/dnode/vnode/src/inc/tq.h
浏览文件 @
7467a54a
...
@@ -115,7 +115,6 @@ typedef struct {
...
@@ -115,7 +115,6 @@ typedef struct {
typedef
struct
{
typedef
struct
{
SMqDataRsp
dataRsp
;
SMqDataRsp
dataRsp
;
SMqRspHead
rspHead
;
char
subKey
[
TSDB_SUBSCRIBE_KEY_LEN
];
char
subKey
[
TSDB_SUBSCRIBE_KEY_LEN
];
SRpcHandleInfo
pInfo
;
SRpcHandleInfo
pInfo
;
}
STqPushEntry
;
}
STqPushEntry
;
...
@@ -183,6 +182,7 @@ int32_t tqOffsetCommitFile(STqOffsetStore* pStore);
...
@@ -183,6 +182,7 @@ int32_t tqOffsetCommitFile(STqOffsetStore* pStore);
// tqSink
// tqSink
void
tqTableSink
(
SStreamTask
*
pTask
,
void
*
vnode
,
int64_t
ver
,
void
*
data
);
void
tqTableSink
(
SStreamTask
*
pTask
,
void
*
vnode
,
int64_t
ver
,
void
*
data
);
void
tqTableSink1
(
SStreamTask
*
pTask
,
void
*
vnode
,
int64_t
ver
,
void
*
data
);
// tqOffset
// tqOffset
char
*
tqOffsetBuildFName
(
const
char
*
path
,
int32_t
ver
);
char
*
tqOffsetBuildFName
(
const
char
*
path
,
int32_t
ver
);
...
...
source/dnode/vnode/src/tq/tq.c
浏览文件 @
7467a54a
...
@@ -192,7 +192,7 @@ int32_t tqPushDataRsp(STQ* pTq, STqPushEntry* pPushEntry) {
...
@@ -192,7 +192,7 @@ int32_t tqPushDataRsp(STQ* pTq, STqPushEntry* pPushEntry) {
return
-
1
;
return
-
1
;
}
}
memcpy
(
buf
,
&
pPushEntry
->
rspH
ead
,
sizeof
(
SMqRspHead
));
memcpy
(
buf
,
&
pPushEntry
->
dataRsp
.
h
ead
,
sizeof
(
SMqRspHead
));
void
*
abuf
=
POINTER_SHIFT
(
buf
,
sizeof
(
SMqRspHead
));
void
*
abuf
=
POINTER_SHIFT
(
buf
,
sizeof
(
SMqRspHead
));
...
@@ -215,7 +215,7 @@ int32_t tqPushDataRsp(STQ* pTq, STqPushEntry* pPushEntry) {
...
@@ -215,7 +215,7 @@ int32_t tqPushDataRsp(STQ* pTq, STqPushEntry* pPushEntry) {
tFormatOffset
(
buf1
,
80
,
&
pRsp
->
reqOffset
);
tFormatOffset
(
buf1
,
80
,
&
pRsp
->
reqOffset
);
tFormatOffset
(
buf2
,
80
,
&
pRsp
->
rspOffset
);
tFormatOffset
(
buf2
,
80
,
&
pRsp
->
rspOffset
);
tqDebug
(
"vgId:%d, from consumer:%"
PRId64
", (epoch %d) push rsp, block num: %d, reqOffset:%s, rspOffset:%s"
,
tqDebug
(
"vgId:%d, from consumer:%"
PRId64
", (epoch %d) push rsp, block num: %d, reqOffset:%s, rspOffset:%s"
,
TD_VID
(
pTq
->
pVnode
),
p
PushEntry
->
rspH
ead
.
consumerId
,
pRsp
->
head
.
epoch
,
pRsp
->
blockNum
,
buf1
,
buf2
);
TD_VID
(
pTq
->
pVnode
),
p
Rsp
->
h
ead
.
consumerId
,
pRsp
->
head
.
epoch
,
pRsp
->
blockNum
,
buf1
,
buf2
);
return
0
;
return
0
;
}
}
...
@@ -560,9 +560,9 @@ int32_t tqProcessPollReq(STQ* pTq, SRpcMsg* pMsg) {
...
@@ -560,9 +560,9 @@ int32_t tqProcessPollReq(STQ* pTq, SRpcMsg* pMsg) {
memcpy
(
pPushEntry
->
subKey
,
pHandle
->
subKey
,
TSDB_SUBSCRIBE_KEY_LEN
);
memcpy
(
pPushEntry
->
subKey
,
pHandle
->
subKey
,
TSDB_SUBSCRIBE_KEY_LEN
);
dataRsp
.
withTbName
=
0
;
dataRsp
.
withTbName
=
0
;
memcpy
(
&
pPushEntry
->
dataRsp
,
&
dataRsp
,
sizeof
(
SMqDataRsp
));
memcpy
(
&
pPushEntry
->
dataRsp
,
&
dataRsp
,
sizeof
(
SMqDataRsp
));
pPushEntry
->
rspH
ead
.
consumerId
=
consumerId
;
pPushEntry
->
dataRsp
.
h
ead
.
consumerId
=
consumerId
;
pPushEntry
->
rspH
ead
.
epoch
=
reqEpoch
;
pPushEntry
->
dataRsp
.
h
ead
.
epoch
=
reqEpoch
;
pPushEntry
->
rspH
ead
.
mqMsgType
=
TMQ_MSG_TYPE__POLL_RSP
;
pPushEntry
->
dataRsp
.
h
ead
.
mqMsgType
=
TMQ_MSG_TYPE__POLL_RSP
;
taosHashPut
(
pTq
->
pPushMgr
,
pHandle
->
subKey
,
strlen
(
pHandle
->
subKey
)
+
1
,
&
pPushEntry
,
sizeof
(
void
*
));
taosHashPut
(
pTq
->
pPushMgr
,
pHandle
->
subKey
,
strlen
(
pHandle
->
subKey
)
+
1
,
&
pPushEntry
,
sizeof
(
void
*
));
tqDebug
(
"tmq poll: consumer %ld, subkey %s, vg %d save handle to push mgr"
,
consumerId
,
pHandle
->
subKey
,
tqDebug
(
"tmq poll: consumer %ld, subkey %s, vg %d save handle to push mgr"
,
consumerId
,
pHandle
->
subKey
,
TD_VID
(
pTq
->
pVnode
));
TD_VID
(
pTq
->
pVnode
));
...
@@ -924,7 +924,7 @@ int32_t tqExpandTask(STQ* pTq, SStreamTask* pTask) {
...
@@ -924,7 +924,7 @@ int32_t tqExpandTask(STQ* pTq, SStreamTask* pTask) {
pTask
->
smaSink
.
smaSink
=
smaHandleRes
;
pTask
->
smaSink
.
smaSink
=
smaHandleRes
;
}
else
if
(
pTask
->
outputType
==
TASK_OUTPUT__TABLE
)
{
}
else
if
(
pTask
->
outputType
==
TASK_OUTPUT__TABLE
)
{
pTask
->
tbSink
.
vnode
=
pTq
->
pVnode
;
pTask
->
tbSink
.
vnode
=
pTq
->
pVnode
;
pTask
->
tbSink
.
tbSinkFunc
=
tqTableSink
;
pTask
->
tbSink
.
tbSinkFunc
=
tqTableSink
1
;
ASSERT
(
pTask
->
tbSink
.
pSchemaWrapper
);
ASSERT
(
pTask
->
tbSink
.
pSchemaWrapper
);
ASSERT
(
pTask
->
tbSink
.
pSchemaWrapper
->
pSchema
);
ASSERT
(
pTask
->
tbSink
.
pSchemaWrapper
->
pSchema
);
...
...
source/dnode/vnode/src/tq/tqSink.c
浏览文件 @
7467a54a
...
@@ -284,6 +284,212 @@ SSubmitReq* tqBlockToSubmit(SVnode* pVnode, const SArray* pBlocks, const STSchem
...
@@ -284,6 +284,212 @@ SSubmitReq* tqBlockToSubmit(SVnode* pVnode, const SArray* pBlocks, const STSchem
return
ret
;
return
ret
;
}
}
void
tqTableSink1
(
SStreamTask
*
pTask
,
void
*
vnode
,
int64_t
ver
,
void
*
data
)
{
const
SArray
*
pBlocks
=
(
const
SArray
*
)
data
;
SVnode
*
pVnode
=
(
SVnode
*
)
vnode
;
int64_t
suid
=
pTask
->
tbSink
.
stbUid
;
char
*
stbFullName
=
pTask
->
tbSink
.
stbFullName
;
STSchema
*
pTSchema
=
pTask
->
tbSink
.
pTSchema
;
SSchemaWrapper
*
pSchemaWrapper
=
pTask
->
tbSink
.
pSchemaWrapper
;
int32_t
blockSz
=
taosArrayGetSize
(
pBlocks
);
bool
createTb
=
true
;
SArray
*
tagArray
=
taosArrayInit
(
1
,
sizeof
(
STagVal
));
if
(
!
tagArray
)
{
terrno
=
TSDB_CODE_OUT_OF_MEMORY
;
return
;
}
tqDebug
(
"vgId:%d, task %d write into table, block num: %d"
,
TD_VID
(
pVnode
),
pTask
->
taskId
,
blockSz
);
for
(
int32_t
i
=
0
;
i
<
blockSz
;
i
++
)
{
SSDataBlock
*
pDataBlock
=
taosArrayGet
(
pBlocks
,
i
);
if
(
pDataBlock
->
info
.
type
==
STREAM_DELETE_RESULT
)
{
SBatchDeleteReq
deleteReq
=
{
0
};
deleteReq
.
deleteReqs
=
taosArrayInit
(
0
,
sizeof
(
SSingleDeleteReq
));
deleteReq
.
suid
=
suid
;
tqBuildDeleteReq
(
pVnode
,
stbFullName
,
pDataBlock
,
&
deleteReq
);
int32_t
len
;
int32_t
code
;
tEncodeSize
(
tEncodeSBatchDeleteReq
,
&
deleteReq
,
len
,
code
);
if
(
code
<
0
)
{
//
ASSERT
(
0
);
}
SEncoder
encoder
;
void
*
serializedDeleteReq
=
rpcMallocCont
(
len
+
sizeof
(
SMsgHead
));
void
*
abuf
=
POINTER_SHIFT
(
serializedDeleteReq
,
sizeof
(
SMsgHead
));
tEncoderInit
(
&
encoder
,
abuf
,
len
);
tEncodeSBatchDeleteReq
(
&
encoder
,
&
deleteReq
);
tEncoderClear
(
&
encoder
);
taosArrayDestroy
(
deleteReq
.
deleteReqs
);
((
SMsgHead
*
)
serializedDeleteReq
)
->
vgId
=
pVnode
->
config
.
vgId
;
SRpcMsg
msg
=
{
.
msgType
=
TDMT_VND_BATCH_DEL
,
.
pCont
=
serializedDeleteReq
,
.
contLen
=
len
+
sizeof
(
SMsgHead
),
};
if
(
tmsgPutToQueue
(
&
pVnode
->
msgCb
,
WRITE_QUEUE
,
&
msg
)
!=
0
)
{
rpcFreeCont
(
serializedDeleteReq
);
tqDebug
(
"failed to put delete req into write-queue since %s"
,
terrstr
());
}
}
else
{
SVCreateTbReq
createTbReq
=
{
0
};
// set const
createTbReq
.
flags
=
0
;
createTbReq
.
type
=
TSDB_CHILD_TABLE
;
createTbReq
.
ctb
.
suid
=
suid
;
// set super table name
SName
name
=
{
0
};
tNameFromString
(
&
name
,
stbFullName
,
T_NAME_ACCT
|
T_NAME_DB
|
T_NAME_TABLE
);
createTbReq
.
ctb
.
stbName
=
strdup
((
char
*
)
tNameGetTableName
(
&
name
));
// strdup(stbFullName);
// set tag content
taosArrayClear
(
tagArray
);
STagVal
tagVal
=
{
.
cid
=
taosArrayGetSize
(
pDataBlock
->
pDataBlock
)
+
1
,
.
type
=
TSDB_DATA_TYPE_UBIGINT
,
.
i64
=
(
int64_t
)
pDataBlock
->
info
.
groupId
,
};
taosArrayPush
(
tagArray
,
&
tagVal
);
createTbReq
.
ctb
.
tagNum
=
taosArrayGetSize
(
tagArray
);
STag
*
pTag
=
NULL
;
tTagNew
(
tagArray
,
1
,
false
,
&
pTag
);
if
(
pTag
==
NULL
)
{
terrno
=
TSDB_CODE_OUT_OF_MEMORY
;
taosArrayDestroy
(
tagArray
);
tdDestroySVCreateTbReq
(
&
createTbReq
);
return
;
}
createTbReq
.
ctb
.
pTag
=
(
uint8_t
*
)
pTag
;
// set tag name
SArray
*
tagName
=
taosArrayInit
(
1
,
TSDB_COL_NAME_LEN
);
char
tagNameStr
[
TSDB_COL_NAME_LEN
]
=
{
0
};
strcpy
(
tagNameStr
,
"group_id"
);
taosArrayPush
(
tagName
,
tagNameStr
);
createTbReq
.
ctb
.
tagName
=
tagName
;
// set table name
if
(
pDataBlock
->
info
.
parTbName
[
0
])
{
createTbReq
.
name
=
strdup
(
pDataBlock
->
info
.
parTbName
);
}
else
{
createTbReq
.
name
=
buildCtbNameByGroupId
(
stbFullName
,
pDataBlock
->
info
.
groupId
);
}
int32_t
schemaLen
;
int32_t
code
;
tEncodeSize
(
tEncodeSVCreateTbReq
,
&
createTbReq
,
schemaLen
,
code
);
if
(
code
<
0
)
{
tdDestroySVCreateTbReq
(
&
createTbReq
);
taosArrayDestroy
(
tagArray
);
return
;
}
// save schema str
void
*
schemaStr
=
taosMemoryMalloc
(
schemaLen
);
if
(
schemaStr
==
NULL
)
{
terrno
=
TSDB_CODE_OUT_OF_MEMORY
;
tdDestroySVCreateTbReq
(
&
createTbReq
);
taosArrayDestroy
(
tagArray
);
return
;
}
SEncoder
encoder
=
{
0
};
tEncoderInit
(
&
encoder
,
schemaStr
,
schemaLen
);
code
=
tEncodeSVCreateTbReq
(
&
encoder
,
&
createTbReq
);
if
(
code
<
0
)
{
terrno
=
TSDB_CODE_OUT_OF_MEMORY
;
tdDestroySVCreateTbReq
(
&
createTbReq
);
taosArrayDestroy
(
tagArray
);
tEncoderClear
(
&
encoder
);
taosMemoryFree
(
schemaStr
);
return
;
}
tEncoderClear
(
&
encoder
);
tdDestroySVCreateTbReq
(
&
createTbReq
);
int32_t
cap
=
sizeof
(
SSubmitReq
);
int32_t
rows
=
pDataBlock
->
info
.
rows
;
int32_t
maxLen
=
TD_ROW_MAX_BYTES_FROM_SCHEMA
(
pTSchema
);
cap
+=
sizeof
(
SSubmitBlk
)
+
schemaLen
+
rows
*
maxLen
;
SSubmitReq
*
ret
=
rpcMallocCont
(
cap
);
ret
->
header
.
vgId
=
pVnode
->
config
.
vgId
;
ret
->
length
=
sizeof
(
SSubmitReq
);
ret
->
numOfBlocks
=
htonl
(
1
);
SSubmitBlk
*
blkHead
=
POINTER_SHIFT
(
ret
,
sizeof
(
SSubmitReq
));
blkHead
->
numOfRows
=
htonl
(
pDataBlock
->
info
.
rows
);
blkHead
->
sversion
=
htonl
(
pTSchema
->
version
);
blkHead
->
suid
=
htobe64
(
suid
);
// uid is assigned by vnode
blkHead
->
uid
=
0
;
blkHead
->
schemaLen
=
0
;
tqDebug
(
"tq sink, convert block %d, rows: %d"
,
i
,
rows
);
int32_t
dataLen
=
0
;
void
*
blkSchema
=
POINTER_SHIFT
(
blkHead
,
sizeof
(
SSubmitBlk
));
STSRow
*
rowData
=
blkSchema
;
if
(
createTb
)
{
memcpy
(
blkSchema
,
schemaStr
,
schemaLen
);
blkHead
->
schemaLen
=
htonl
(
schemaLen
);
rowData
=
POINTER_SHIFT
(
blkSchema
,
schemaLen
);
}
taosMemoryFree
(
schemaStr
);
for
(
int32_t
j
=
0
;
j
<
rows
;
j
++
)
{
SRowBuilder
rb
=
{
0
};
tdSRowInit
(
&
rb
,
pTSchema
->
version
);
tdSRowSetTpInfo
(
&
rb
,
pTSchema
->
numOfCols
,
pTSchema
->
flen
);
tdSRowResetBuf
(
&
rb
,
rowData
);
for
(
int32_t
k
=
0
;
k
<
pTSchema
->
numOfCols
;
k
++
)
{
const
STColumn
*
pColumn
=
&
pTSchema
->
columns
[
k
];
SColumnInfoData
*
pColData
=
taosArrayGet
(
pDataBlock
->
pDataBlock
,
k
);
if
(
colDataIsNull_s
(
pColData
,
j
))
{
tdAppendColValToRow
(
&
rb
,
pColumn
->
colId
,
pColumn
->
type
,
TD_VTYPE_NULL
,
NULL
,
false
,
pColumn
->
offset
,
k
);
}
else
{
void
*
colData
=
colDataGetData
(
pColData
,
j
);
tdAppendColValToRow
(
&
rb
,
pColumn
->
colId
,
pColumn
->
type
,
TD_VTYPE_NORM
,
colData
,
true
,
pColumn
->
offset
,
k
);
}
}
tdSRowEnd
(
&
rb
);
int32_t
rowLen
=
TD_ROW_LEN
(
rowData
);
rowData
=
POINTER_SHIFT
(
rowData
,
rowLen
);
dataLen
+=
rowLen
;
}
blkHead
->
dataLen
=
htonl
(
dataLen
);
ret
->
length
+=
sizeof
(
SSubmitBlk
)
+
schemaLen
+
dataLen
;
ret
->
length
=
htonl
(
ret
->
length
);
SRpcMsg
msg
=
{
.
msgType
=
TDMT_VND_SUBMIT
,
.
pCont
=
ret
,
.
contLen
=
ntohl
(
ret
->
length
),
};
if
(
tmsgPutToQueue
(
&
pVnode
->
msgCb
,
WRITE_QUEUE
,
&
msg
)
!=
0
)
{
rpcFreeCont
(
ret
);
tqDebug
(
"failed to put into write-queue since %s"
,
terrstr
());
}
}
}
taosArrayDestroy
(
tagArray
);
}
void
tqTableSink
(
SStreamTask
*
pTask
,
void
*
vnode
,
int64_t
ver
,
void
*
data
)
{
void
tqTableSink
(
SStreamTask
*
pTask
,
void
*
vnode
,
int64_t
ver
,
void
*
data
)
{
const
SArray
*
pRes
=
(
const
SArray
*
)
data
;
const
SArray
*
pRes
=
(
const
SArray
*
)
data
;
SVnode
*
pVnode
=
(
SVnode
*
)
vnode
;
SVnode
*
pVnode
=
(
SVnode
*
)
vnode
;
...
...
tests/system-test/7-tmq/tmqAutoCreateTbl.py
浏览文件 @
7467a54a
...
@@ -52,7 +52,7 @@ class TDTestCase:
...
@@ -52,7 +52,7 @@ class TDTestCase:
paraDict
[
'rowsPerTbl'
]
=
self
.
rowsPerTbl
paraDict
[
'rowsPerTbl'
]
=
self
.
rowsPerTbl
tmqCom
.
initConsumerTable
()
tmqCom
.
initConsumerTable
()
tdCom
.
create_database
(
tdSql
,
paraDict
[
"dbName"
],
paraDict
[
"dropFlag"
],
vgroups
=
paraDict
[
"vgroups"
],
replica
=
1
)
tdCom
.
create_database
(
tdSql
,
paraDict
[
"dbName"
],
paraDict
[
"dropFlag"
],
vgroups
=
paraDict
[
"vgroups"
],
replica
=
1
,
wal_retention_size
=-
1
,
wal_retention_period
=-
1
)
tdLog
.
info
(
"create stb"
)
tdLog
.
info
(
"create stb"
)
tmqCom
.
create_stable
(
tdSql
,
dbName
=
paraDict
[
"dbName"
],
stbName
=
paraDict
[
"stbName"
])
tmqCom
.
create_stable
(
tdSql
,
dbName
=
paraDict
[
"dbName"
],
stbName
=
paraDict
[
"stbName"
])
# tdLog.info("create ctb")
# tdLog.info("create ctb")
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录