Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
阿啄debugIT
libfastcommon
提交
f5028fcb
L
libfastcommon
项目概览
阿啄debugIT
/
libfastcommon
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
L
libfastcommon
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
f5028fcb
编写于
11月 23, 2020
作者:
Y
YuQing
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
fast_timer.[hc]: support lock for option
上级
58e1aea3
变更
5
隐藏空白更改
内联
并排
Showing
5 changed file
with
307 addition
and
172 deletion
+307
-172
HISTORY
HISTORY
+2
-1
src/fast_task_queue.h
src/fast_task_queue.h
+9
-3
src/fast_timer.c
src/fast_timer.c
+261
-132
src/fast_timer.h
src/fast_timer.h
+25
-14
src/ioevent_loop.c
src/ioevent_loop.c
+10
-22
未找到文件。
HISTORY
浏览文件 @
f5028fcb
Version 1.44 2020-11-
0
3
Version 1.44 2020-11-
2
3
* add test file src/tests/test_pthread_lock.c
* add uniq_skiplist.[hc]
* add function split_string_ex
...
...
@@ -37,6 +37,7 @@ Version 1.44 2020-11-03
* shared_func.[hc]: add function fc_path_contains
* fast_mblock.[hc]: support alloc elements limit
* sockopt.[hc]: add function asyncconnectserverbyip
* fast_timer.[hc]: support lock for option
Version 1.43 2019-12-25
* replace function call system to getExecResult,
...
...
src/fast_task_queue.h
浏览文件 @
f5028fcb
...
...
@@ -45,9 +45,9 @@ struct fast_task_info;
typedef
struct
ioevent_entry
{
int
fd
;
FastTimerEntry
timer
;
IOEventCallback
callback
;
FastTimerEntry
timer
;
//must first
int
fd
;
IOEventCallback
callback
;
}
IOEventEntry
;
struct
nio_thread_data
...
...
@@ -70,6 +70,12 @@ struct nio_thread_data
}
notify
;
//for thread notify
};
struct
ioevent_notify_entry
{
IOEventEntry
event
;
//must first
struct
nio_thread_data
*
thread_data
;
};
struct
fast_task_info
{
IOEventEntry
event
;
//must first
...
...
src/fast_timer.c
浏览文件 @
f5028fcb
...
...
@@ -20,174 +20,303 @@
#include <errno.h>
#include "logger.h"
#include "fc_memory.h"
#include "pthread_func.h"
#include "fast_timer.h"
int
fast_timer_init
(
FastTimer
*
timer
,
const
int
slot_count
,
const
int64_t
current_time
)
static
int
fast_timer_init_locks
(
FastTimer
*
timer
)
{
int
bytes
;
if
(
slot_count
<=
0
||
current_time
<=
0
)
{
return
EINVAL
;
}
timer
->
slot_count
=
slot_count
;
timer
->
base_time
=
current_time
;
//base time for slot 0
timer
->
current_time
=
current_time
;
bytes
=
sizeof
(
FastTimerSlot
)
*
slot_count
;
timer
->
slots
=
(
FastTimerSlot
*
)
fc_malloc
(
bytes
);
if
(
timer
->
slots
==
NULL
)
{
return
ENOMEM
;
}
memset
(
timer
->
slots
,
0
,
bytes
);
return
0
;
int
result
;
FastTimerSlot
*
slot
;
FastTimerSlot
*
end
;
end
=
timer
->
slots
+
timer
->
slot_count
;
for
(
slot
=
timer
->
slots
;
slot
<
end
;
slot
++
)
{
if
((
result
=
init_pthread_lock
(
&
slot
->
lock
))
!=
0
)
{
return
result
;
}
}
return
0
;
}
int
fast_timer_init_ex
(
FastTimer
*
timer
,
const
int
slot_count
,
const
int64_t
current_time
,
const
bool
need_lock
)
{
int
result
;
int
bytes
;
if
(
slot_count
<=
0
||
current_time
<=
0
)
{
return
EINVAL
;
}
timer
->
need_lock
=
need_lock
;
timer
->
slot_count
=
slot_count
;
timer
->
base_time
=
current_time
;
//base time for slot 0
timer
->
current_time
=
current_time
;
bytes
=
sizeof
(
FastTimerSlot
)
*
slot_count
;
timer
->
slots
=
(
FastTimerSlot
*
)
fc_malloc
(
bytes
);
if
(
timer
->
slots
==
NULL
)
{
return
ENOMEM
;
}
memset
(
timer
->
slots
,
0
,
bytes
);
if
(
need_lock
)
{
if
((
result
=
fast_timer_init_locks
(
timer
))
!=
0
)
{
return
result
;
}
}
return
0
;
}
void
fast_timer_destroy
(
FastTimer
*
timer
)
{
if
(
timer
->
slots
!=
NULL
)
{
free
(
timer
->
slots
);
timer
->
slots
=
NULL
;
}
if
(
timer
->
slots
!=
NULL
)
{
if
(
timer
->
need_lock
)
{
FastTimerSlot
*
slot
;
FastTimerSlot
*
end
;
end
=
timer
->
slots
+
timer
->
slot_count
;
for
(
slot
=
timer
->
slots
;
slot
<
end
;
slot
++
)
{
pthread_mutex_destroy
(
&
slot
->
lock
);
}
}
free
(
timer
->
slots
);
timer
->
slots
=
NULL
;
}
}
#define TIMER_CHECK_LOCK(timer, slot) \
do { \
if (timer->need_lock) { \
PTHREAD_MUTEX_LOCK(&(slot)->lock); \
} \
} while (0)
#define TIMER_CHECK_UNLOCK(timer, slot) \
do { \
if (timer->need_lock) { \
PTHREAD_MUTEX_UNLOCK(&(slot)->lock); \
} \
} while (0)
#define TIMER_CHECK_LOCK_AND_SET_SLOT(timer, slot, entry) \
do { \
if (timer->need_lock) { \
PTHREAD_MUTEX_LOCK(&(slot)->lock); \
entry->slot_index = slot - timer->slots; \
} \
} while (0)
#define TIMER_CHECK_LOCK_BY_ENTRY(timer, entry) \
do { \
if (timer->need_lock && entry->slot_index >= 0) { \
PTHREAD_MUTEX_LOCK(&(timer->slots + entry->slot_index)->lock); \
} \
} while (0)
#define TIMER_CHECK_UNLOCK_AND_REMOVE_BY_ENTRY(timer, entry) \
do { \
if (timer->need_lock && entry->slot_index >= 0) { \
PTHREAD_MUTEX_UNLOCK(&(timer->slots + entry->slot_index)->lock); \
entry->slot_index = -1; \
} \
} while (0)
#define TIMER_CHECK_LOCK_BY_SINDEX(timer, slot_index) \
do { \
if (timer->need_lock) { \
PTHREAD_MUTEX_LOCK(&(timer->slots + slot_index)->lock); \
} \
} while (0)
#define TIMER_CHECK_UNLOCK_BY_SINDEX(timer, slot_index) \
do { \
if (timer->need_lock) { \
PTHREAD_MUTEX_UNLOCK(&(timer->slots + slot_index)->lock); \
} \
} while (0)
#define TIMER_GET_SLOT_INDEX(timer, expires) \
(((expires) - timer->base_time) % timer->slot_count)
#define TIMER_GET_SLOT_POINTER(timer, expires) \
(timer->slots + TIMER_GET_SLOT_INDEX(timer, expires))
int
fast_timer_add
(
FastTimer
*
timer
,
FastTimerEntry
*
entry
)
static
inline
void
add_entry
(
FastTimer
*
timer
,
FastTimerSlot
*
slot
,
FastTimerEntry
*
entry
,
const
int64_t
expires
,
const
bool
set_expires
)
{
TIMER_CHECK_LOCK_AND_SET_SLOT
(
timer
,
slot
,
entry
);
if
(
set_expires
)
{
entry
->
expires
=
expires
;
}
entry
->
next
=
slot
->
head
.
next
;
if
(
slot
->
head
.
next
!=
NULL
)
{
slot
->
head
.
next
->
prev
=
entry
;
}
entry
->
prev
=
&
slot
->
head
;
slot
->
head
.
next
=
entry
;
entry
->
rehash
=
false
;
TIMER_CHECK_UNLOCK
(
timer
,
slot
);
}
void
fast_timer_add_ex
(
FastTimer
*
timer
,
FastTimerEntry
*
entry
,
const
int64_t
expires
,
const
bool
set_expires
)
{
FastTimerSlot
*
slot
;
slot
=
TIMER_GET_SLOT_POINTER
(
timer
,
entry
->
expires
>
timer
->
current_time
?
entry
->
expires
:
timer
->
current_time
);
entry
->
next
=
slot
->
head
.
next
;
if
(
slot
->
head
.
next
!=
NULL
)
{
slot
->
head
.
next
->
prev
=
entry
;
}
entry
->
prev
=
&
slot
->
head
;
slot
->
head
.
next
=
entry
;
entry
->
rehash
=
false
;
return
0
;
FastTimerSlot
*
slot
;
int64_t
new_expires
;
bool
new_set_expires
;
if
(
expires
>
timer
->
current_time
)
{
new_expires
=
expires
;
new_set_expires
=
set_expires
;
}
else
{
new_expires
=
timer
->
current_time
;
new_set_expires
=
true
;
}
slot
=
TIMER_GET_SLOT_POINTER
(
timer
,
new_expires
);
add_entry
(
timer
,
slot
,
entry
,
new_expires
,
new_set_expires
);
}
int
fast_timer_modify
(
FastTimer
*
timer
,
FastTimerEntry
*
entry
,
void
fast_timer_modify
(
FastTimer
*
timer
,
FastTimerEntry
*
entry
,
const
int64_t
new_expires
)
{
if
(
new_expires
==
entry
->
expires
)
{
return
0
;
}
if
(
new_expires
<
entry
->
expires
)
{
fast_timer_remove
(
timer
,
entry
);
entry
->
expires
=
new_expires
;
return
fast_timer_add
(
timer
,
entry
);
}
entry
->
rehash
=
TIMER_GET_SLOT_INDEX
(
timer
,
new_expires
)
!=
TIMER_GET_SLOT_INDEX
(
timer
,
entry
->
expires
);
entry
->
expires
=
new_expires
;
//lazy move
return
0
;
int
slot_index
;
if
(
new_expires
>
entry
->
expires
)
{
if
(
timer
->
need_lock
&&
entry
->
slot_index
>=
0
)
{
slot_index
=
entry
->
slot_index
;
if
(
slot_index
<
0
)
{
slot_index
=
TIMER_GET_SLOT_INDEX
(
timer
,
entry
->
expires
);
}
}
else
{
slot_index
=
TIMER_GET_SLOT_INDEX
(
timer
,
entry
->
expires
);
}
TIMER_CHECK_LOCK_BY_SINDEX
(
timer
,
slot_index
);
entry
->
rehash
=
TIMER_GET_SLOT_INDEX
(
timer
,
new_expires
)
!=
slot_index
;
entry
->
expires
=
new_expires
;
//lazy move
TIMER_CHECK_UNLOCK_BY_SINDEX
(
timer
,
slot_index
);
}
else
if
(
new_expires
<
entry
->
expires
)
{
fast_timer_remove
(
timer
,
entry
);
fast_timer_add_ex
(
timer
,
entry
,
new_expires
,
true
);
}
}
static
inline
void
remove_entry
(
FastTimerEntry
*
entry
)
{
if
(
entry
->
next
!=
NULL
)
{
entry
->
next
->
prev
=
entry
->
prev
;
entry
->
prev
->
next
=
entry
->
next
;
entry
->
next
=
NULL
;
}
else
{
entry
->
prev
->
next
=
NULL
;
}
entry
->
prev
=
NULL
;
}
int
fast_timer_remove
(
FastTimer
*
timer
,
FastTimerEntry
*
entry
)
{
if
(
entry
->
prev
==
NULL
)
{
return
ENOENT
;
//already removed
}
if
(
entry
->
next
!=
NULL
)
{
entry
->
next
->
prev
=
entry
->
prev
;
entry
->
prev
->
next
=
entry
->
next
;
entry
->
next
=
NULL
;
}
else
{
entry
->
prev
->
next
=
NULL
;
}
entry
->
prev
=
NULL
;
return
0
;
int
result
;
TIMER_CHECK_LOCK_BY_ENTRY
(
timer
,
entry
);
if
(
entry
->
prev
==
NULL
)
{
result
=
ENOENT
;
//already removed
}
else
{
remove_entry
(
entry
);
result
=
0
;
}
TIMER_CHECK_UNLOCK_AND_REMOVE_BY_ENTRY
(
timer
,
entry
);
return
result
;
}
FastTimerSlot
*
fast_timer_slot_get
(
FastTimer
*
timer
,
const
int64_t
current_time
)
{
if
(
timer
->
current_time
>=
current_time
)
{
return
NULL
;
}
if
(
timer
->
current_time
>=
current_time
)
{
return
NULL
;
}
return
TIMER_GET_SLOT_POINTER
(
timer
,
timer
->
current_time
++
);
return
TIMER_GET_SLOT_POINTER
(
timer
,
timer
->
current_time
++
);
}
int
fast_timer_timeouts_get
(
FastTimer
*
timer
,
const
int64_t
current_time
,
FastTimerEntry
*
head
)
FastTimerEntry
*
head
)
{
FastTimerSlot
*
slot
;
FastTimerEntry
*
entry
;
FastTimerEntry
*
first
;
FastTimerEntry
*
last
;
FastTimerEntry
*
tail
;
int
count
;
head
->
prev
=
NULL
;
head
->
next
=
NULL
;
if
(
timer
->
current_time
>=
current_time
)
{
return
0
;
}
first
=
NULL
;
last
=
NULL
;
tail
=
head
;
count
=
0
;
while
(
timer
->
current_time
<
current_time
)
{
slot
=
TIMER_GET_SLOT_POINTER
(
timer
,
timer
->
current_time
++
);
entry
=
slot
->
head
.
next
;
while
(
entry
!=
NULL
)
{
if
(
entry
->
expires
>=
current_time
)
{
//not expired
if
(
first
!=
NULL
)
{
first
->
prev
->
next
=
entry
;
entry
->
prev
=
first
->
prev
;
FastTimerSlot
*
slot
;
FastTimerSlot
*
new_slot
;
FastTimerEntry
*
entry
;
FastTimerEntry
*
first
;
FastTimerEntry
*
last
;
FastTimerEntry
*
tail
;
int
count
;
head
->
prev
=
NULL
;
head
->
next
=
NULL
;
if
(
timer
->
current_time
>=
current_time
)
{
return
0
;
}
first
=
NULL
;
last
=
NULL
;
tail
=
head
;
count
=
0
;
while
(
timer
->
current_time
<
current_time
)
{
slot
=
TIMER_GET_SLOT_POINTER
(
timer
,
timer
->
current_time
++
);
TIMER_CHECK_LOCK
(
timer
,
slot
);
entry
=
slot
->
head
.
next
;
while
(
entry
!=
NULL
)
{
if
(
entry
->
expires
>=
current_time
)
{
//not expired
if
(
first
!=
NULL
)
{
first
->
prev
->
next
=
entry
;
entry
->
prev
=
first
->
prev
;
tail
->
next
=
first
;
first
->
prev
=
tail
;
tail
=
last
;
first
=
NULL
;
}
if
(
entry
->
rehash
)
{
last
=
entry
;
entry
=
entry
->
next
;
new_slot
=
TIMER_GET_SLOT_POINTER
(
timer
,
last
->
expires
);
if
(
new_slot
!=
slot
)
{
//check to avoid deadlock
remove_entry
(
last
);
add_entry
(
timer
,
new_slot
,
last
,
last
->
expires
,
false
);
}
else
{
last
->
rehash
=
false
;
}
continue
;
}
}
else
{
//expired
count
++
;
if
(
first
==
NULL
)
{
first
=
entry
;
}
}
last
=
entry
;
entry
=
entry
->
next
;
}
if
(
first
!=
NULL
)
{
first
->
prev
->
next
=
NULL
;
tail
->
next
=
first
;
first
->
prev
=
tail
;
tail
=
last
;
first
=
NULL
;
}
if
(
entry
->
rehash
)
{
last
=
entry
;
entry
=
entry
->
next
;
last
->
rehash
=
false
;
fast_timer_remove
(
timer
,
last
);
fast_timer_add
(
timer
,
last
);
continue
;
}
}
else
{
//expired
count
++
;
if
(
first
==
NULL
)
{
first
=
entry
;
}
}
last
=
entry
;
entry
=
entry
->
next
;
}
if
(
first
!=
NULL
)
{
first
->
prev
->
next
=
NULL
;
tail
->
next
=
first
;
first
->
prev
=
tail
;
tail
=
last
;
first
=
NULL
;
}
}
if
(
count
>
0
)
{
tail
->
next
=
NULL
;
}
return
count
;
}
}
TIMER_CHECK_UNLOCK
(
timer
,
slot
);
}
if
(
count
>
0
)
{
tail
->
next
=
NULL
;
}
return
count
;
}
src/fast_timer.h
浏览文件 @
f5028fcb
...
...
@@ -17,38 +17,49 @@
#define __FAST_TIMER_H__
#include <stdint.h>
#include <pthread.h>
#include "common_define.h"
struct
fast_timer_slot
;
typedef
struct
fast_timer_entry
{
int64_t
expires
;
void
*
data
;
struct
fast_timer_entry
*
prev
;
struct
fast_timer_entry
*
next
;
bool
rehash
;
int64_t
expires
;
struct
fast_timer_entry
*
prev
;
struct
fast_timer_entry
*
next
;
int
slot_index
;
bool
rehash
;
}
FastTimerEntry
;
typedef
struct
fast_timer_slot
{
struct
fast_timer_entry
head
;
struct
fast_timer_entry
head
;
pthread_mutex_t
lock
;
}
FastTimerSlot
;
typedef
struct
fast_timer
{
int
slot_count
;
//time wheel slot count
int64_t
base_time
;
//base time for slot 0
int64_t
current_time
;
FastTimerSlot
*
slots
;
bool
need_lock
;
int
slot_count
;
//time wheel slot count
int64_t
base_time
;
//base time for slot 0
int64_t
current_time
;
FastTimerSlot
*
slots
;
}
FastTimer
;
#ifdef __cplusplus
extern
"C"
{
#endif
int
fast_timer_init
(
FastTimer
*
timer
,
const
int
slot_count
,
const
int64_t
current_time
);
#define fast_timer_init(timer, slot_count, current_time) \
fast_timer_init_ex(timer, slot_count, current_time, false)
#define fast_timer_add(timer, entry) \
fast_timer_add_ex(timer, entry, (entry)->expires, false)
int
fast_timer_init_ex
(
FastTimer
*
timer
,
const
int
slot_count
,
const
int64_t
current_time
,
const
bool
need_lock
);
void
fast_timer_destroy
(
FastTimer
*
timer
);
int
fast_timer_add
(
FastTimer
*
timer
,
FastTimerEntry
*
entry
);
void
fast_timer_add_ex
(
FastTimer
*
timer
,
FastTimerEntry
*
entry
,
const
int64_t
expires
,
const
bool
set_expires
);
int
fast_timer_remove
(
FastTimer
*
timer
,
FastTimerEntry
*
entry
);
int
fast_timer_modify
(
FastTimer
*
timer
,
FastTimerEntry
*
entry
,
void
fast_timer_modify
(
FastTimer
*
timer
,
FastTimerEntry
*
entry
,
const
int64_t
new_expires
);
FastTimerSlot
*
fast_timer_slot_get
(
FastTimer
*
timer
,
const
int64_t
current_time
);
...
...
src/ioevent_loop.c
浏览文件 @
f5028fcb
...
...
@@ -29,7 +29,7 @@ static void deal_ioevents(IOEventPoller *ioevent)
pEntry
=
(
IOEventEntry
*
)
IOEVENT_GET_DATA
(
ioevent
,
ioevent
->
iterator
.
index
);
if
(
pEntry
!=
NULL
)
{
pEntry
->
callback
(
pEntry
->
fd
,
event
,
pEntry
->
timer
.
data
);
pEntry
->
callback
(
pEntry
->
fd
,
event
,
pEntry
);
}
else
{
logDebug
(
"file: "
__FILE__
", line: %d, "
...
...
@@ -51,7 +51,7 @@ int ioevent_remove(IOEventPoller *ioevent, void *data)
pEntry
=
(
IOEventEntry
*
)
IOEVENT_GET_DATA
(
ioevent
,
ioevent
->
iterator
.
index
);
if
(
pEntry
!=
NULL
&&
pEntry
->
timer
.
data
==
data
)
{
if
(
pEntry
!=
NULL
&&
(
void
*
)
pEntry
==
data
)
{
return
0
;
//do NOT clear current entry
}
...
...
@@ -59,7 +59,7 @@ int ioevent_remove(IOEventPoller *ioevent, void *data)
index
++
)
{
pEntry
=
(
IOEventEntry
*
)
IOEVENT_GET_DATA
(
ioevent
,
index
);
if
(
pEntry
!=
NULL
&&
pEntry
->
timer
.
data
==
data
)
{
if
(
pEntry
!=
NULL
&&
(
void
*
)
pEntry
==
data
)
{
logDebug
(
"file: "
__FILE__
", line: %d, "
"clear ioevent data: %p"
,
__LINE__
,
data
);
IOEVENT_CLEAR_DATA
(
ioevent
,
index
);
...
...
@@ -83,11 +83,10 @@ static void deal_timeouts(FastTimerEntry *head)
entry
=
entry
->
next
;
current
->
prev
=
current
->
next
=
NULL
;
//must set NULL because NOT in time wheel
pEventEntry
=
(
IOEventEntry
*
)
current
->
data
;
pEventEntry
=
(
IOEventEntry
*
)
current
;
if
(
pEventEntry
!=
NULL
)
{
pEventEntry
->
callback
(
pEventEntry
->
fd
,
IOEVENT_TIMEOUT
,
current
->
data
);
pEventEntry
->
callback
(
pEventEntry
->
fd
,
IOEVENT_TIMEOUT
,
current
);
}
}
}
...
...
@@ -97,16 +96,16 @@ int ioevent_loop(struct nio_thread_data *pThreadData,
clean_up_callback
,
volatile
bool
*
continue_flag
)
{
int
result
;
IOEventE
ntry
ev_notify
;
struct
ioevent_notify_e
ntry
ev_notify
;
FastTimerEntry
head
;
struct
fast_task_info
*
task
;
time_t
last_check_time
;
int
count
;
memset
(
&
ev_notify
,
0
,
sizeof
(
ev_notify
));
ev_notify
.
fd
=
FC_NOTIFY_READ_FD
(
pThreadData
);
ev_notify
.
callback
=
recv_notify_callback
;
ev_notify
.
t
imer
.
data
=
pThreadData
;
ev_notify
.
event
.
fd
=
FC_NOTIFY_READ_FD
(
pThreadData
);
ev_notify
.
event
.
callback
=
recv_notify_callback
;
ev_notify
.
t
hread_
data
=
pThreadData
;
if
(
ioevent_attach
(
&
pThreadData
->
ev_puller
,
pThreadData
->
pipe_fds
[
0
],
IOEVENT_READ
,
&
ev_notify
)
!=
0
)
...
...
@@ -210,18 +209,7 @@ int ioevent_set(struct fast_task_info *task, struct nio_thread_data *pThread,
return
result
;
}
task
->
event
.
timer
.
data
=
task
;
task
->
event
.
timer
.
expires
=
g_current_time
+
timeout
;
result
=
fast_timer_add
(
&
pThread
->
timer
,
&
task
->
event
.
timer
);
if
(
result
!=
0
)
{
logError
(
"file: "
__FILE__
", line: %d, "
\
"fast_timer_add fail, "
\
"errno: %d, error info: %s"
,
\
__LINE__
,
result
,
STRERROR
(
result
));
return
result
;
}
fast_timer_add
(
&
pThread
->
timer
,
&
task
->
event
.
timer
);
return
0
;
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录