1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-02-13 03:41:55 +00:00

Support MIPS 64bits for loongson 3A4000/3B3000. v5.0.34

This commit is contained in:
winlin 2022-08-06 13:03:45 +08:00
parent aba6667357
commit 1589858cb0
16 changed files with 543 additions and 185 deletions

View file

@ -35,6 +35,8 @@ For Linux aarch64, which fail with `Unknown CPU architecture`:
make linux-debug EXTRA_CFLAGS="-D__aarch64__"
```
> Note: For more CPU architectures, please see [#22](https://github.com/ossrs/state-threads/issues/22)
Linux with valgrind:
```bash
@ -110,6 +112,7 @@ The branch [srs](https://github.com/ossrs/state-threads/tree/srs) will be patche
- [x] LOONGARCH: Support loongarch for loongson CPU, [#24](https://github.com/ossrs/state-threads/issues/24).
- [x] System: Support Multiple Threads for Linux and Darwin. [#19](https://github.com/ossrs/state-threads/issues/19), [srs#2188](https://github.com/ossrs/srs/issues/2188).
- [x] RISCV: Support RISCV for RISCV CPU, [#24](https://github.com/ossrs/state-threads/pull/28).
- [x] MIPS: Support Linux/MIPS64 for loongson 3A4000/3B3000, [#21](https://github.com/ossrs/state-threads/pull/21).
- [ ] IDE: Support CLion for debugging and learning.
- [ ] System: Support sendmmsg for UDP, [#12](https://github.com/ossrs/state-threads/issues/12).

View file

@ -1,52 +1,38 @@
#!/bin/bash
# In .circleci/config.yml, generate *.gcno with
# ./configure --gcov --without-research --without-librtmp && make
# and generate *.gcda by
# ./objs/srs_utest
# Workdir is objs/cover.
workdir=`pwd`/codecov && rm -rf $workdir
# Tool git is required to map the right path.
git --version >/dev/null 2>&1
ret=$?; if [[ $ret -ne 0 ]]; then echo "Tool git is required, ret=$ret"; exit $ret; fi
# Workdir is obj/coverage.
workdir=`pwd`/coverage
# Create trunk under workdir.
mkdir -p $workdir && cd $workdir
ret=$?; if [[ $ret -ne 0 ]]; then echo "Enter workdir failed, ret=$ret"; exit $ret; fi
# Collect all *.gcno and *.gcda to objs/cover.
cd $workdir && for file in $(cd .. && ls *.c); do
cp ../$file $file && echo "Copy $file" &&
if [[ -f ../obj/${file%.*}.gcno ]]; then
cp ../obj/${file%.*}.* .
fi
done
ret=$?; if [[ $ret -ne 0 ]]; then echo "Collect *.gcno and *.gcda failed, ret=$ret"; exit $ret; fi
# Generate *.gcov for coverage.
cd $workdir &&
for file in $(ls *.c); do
gcov $file -o `dirname $file`
ret=$?; if [[ $ret -ne 0 ]]; then echo "Collect $file failed, ret=$ret"; exit $ret; fi
done
# Filter the gcov files, remove utest or gtest.
cd $workdir &&
rm -f *gtest*.gcov *utest*.gcov
ret=$?; if [[ $ret -ne 0 ]]; then echo "Cook gcov files failed, ret=$ret"; exit $ret; fi
CODECOV_ARGS=""
if [[ $ST_PROJECT != '' ]]; then
# -R root dir Used when not in git/hg project to identify project root directory
# -p dir Project root directory. Also used when preparing gcov
CODECOV_ARGS="$CODECOV_ARGS -R $ST_PROJECT -p $ST_PROJECT"
fi
if [[ $ST_BRANCH != '' ]]; then
# -B branch Specify the branch name
CODECOV_ARGS="$CODECOV_ARGS -B $ST_BRANCH"
fi
if [[ $ST_SHA != '' ]]; then
# -C sha Specify the commit sha
CODECOV_ARGS="$CODECOV_ARGS -C $ST_SHA"
fi
if [[ $ST_PR != '' ]]; then
# -P pr Specify the pull request number
CODECOV_ARGS="$CODECOV_ARGS -P $ST_PR"
fi
# Upload report with *.gcov
# Remark: The file codecov.yml is not neccessary. It literally depends on git.
# Note: The right path is like:
# https://codecov.io/gh/ossrs/srs/src/3.0release/trunk/src/protocol/srs_rtmp_stack.cpp
# https://codecov.io/gh/ossrs/srs/src/20fbb4466fdc8ba5d810b8570df6004063212838/trunk/src/protocol/srs_rtmp_stack.cpp
# Remark: It takes a few minutes to sync with github, so it might not available when CircleCI is done.
# https://circleci.com/gh/ossrs/srs/tree/3.0release
#
# Note: Use '-X gcov' to avoid generate the gcov files again.
# https://app.codecov.io/gh/ossrs/state-threads/blob/srs/sched.c
# https://app.codecov.io/gh/ossrs/state-threads/blob/593cf748f055ca383867003e409a423efd8f8f86/sched.c
cd $workdir &&
export CODECOV_TOKEN="0d616496-f781-4e7c-b285-d1f70a1cdf24" &&
bash <(curl -s https://codecov.io/bash) -X gcov &&
export CODECOV_TOKEN="$CODECOV_TOKEN" &&
bash <(curl -s https://codecov.io/bash) $CODECOV_ARGS -f '!*gtest*' -f '!*c++*' &&
echo "Done" && exit 0

View file

@ -1,23 +1,14 @@
#!/bin/bash
if [[ ! -f utest/gtest/include/gtest/gtest.h ]]; then
(
cd utest && rm -rf gtest &&
curl https://github.com/google/googletest/archive/release-1.6.0.tar.gz -L -o googletest-release-1.6.0.tar.gz &&
tar xf googletest-release-1.6.0.tar.gz &&
ln -sf googletest-release-1.6.0 gtest &&
echo "Setup gtest ok"
)
fi
if [[ ! -f utest/gtest/include/gtest/gtest.h ]]; then
echo "No utest/gtest, please download from https://github.com/google/googletest/releases/tag/release-1.6.0"
if [[ ! -f utest/gtest-fit/googletest/include/gtest/gtest.h ]]; then
echo "No utest/gtest, please download from https://github.com/google/googletest/releases/tag/release-1.11.0"
exit -1
else
echo "Check utest/gtest ok"
fi
if [[ $(gcovr --version >/dev/null && echo yes) != yes ]]; then
echo "Please install gcovr: https://github.com/ossrs/state-threads/tree/srs#utest-and-coverage"
echo "Please install gcovr"
exit -1
fi
@ -34,6 +25,9 @@ fi
echo "Generating coverage"
mkdir -p coverage &&
gcovr -r . -e LINUX -e DARWIN -e examples --html --html-details -o coverage/st.html &&
(cd obj && rm -f gtest-all.gcda gtest-all.gcno) &&
(cd obj && rm -f *.c *.cpp gtest-fit && ln -sf ../*.c . && ln -sf ../utest/*.cpp && ln -sf ../utest/gtest-fit .) &&
(cd obj && gcovr --gcov-exclude gtest --html --html-details -o ../coverage/st.html) &&
(cd obj && rm -f *.c *.cpp gtest-fit) &&
echo "Coverage report at coverage/st.html" &&
open coverage/st.html

View file

@ -22,19 +22,12 @@ ret=$?; if [[ 0 -ne $ret ]]; then echo "Make ST utest fail, ret=$ret"; exit $ret
echo "Generating coverage"
mkdir -p coverage &&
gcovr -r . -e LINUX -e DARWIN -e examples --html --html-details -o coverage/st.html &&
(cd obj && rm -f gtest-all.gcda gtest-all.gcno) &&
(cd obj && rm -f *.c *.cpp gtest-fit && ln -sf ../*.c . && ln -sf ../utest/*.cpp && ln -sf ../utest/gtest-fit .) &&
(cd obj && gcovr --gcov-exclude gtest --html --html-details -o ../coverage/st.html) &&
(cd obj && rm -f *.c *.cpp gtest-fit) &&
echo "Coverage report at coverage/st.html" &&
open coverage/st.html
popd
echo "UTest done, restore $(pwd)"
cat << END > /dev/stdout
# CLI For DARWIN
cd $PWD && rm -f ./obj/*.gcda &&
make darwin-debug-gcov && ./obj/st_utest &&
mkdir -p coverage && gcovr -r . -e LINUX -e DARWIN -e examples --html --html-details -o coverage/st.html &&
open coverage/st.html
END
echo "UTest done, restore $(pwd)"

View file

@ -182,6 +182,10 @@
#error "ARM/Linux pre-glibc2 not supported yet"
#endif /* defined(__GLIBC__) && __GLIBC__ >= 2 */
#elif defined(__mips64)
/* https://github.com/ossrs/state-threads/issues/21 */
#define MD_USE_BUILTIN_SETJMP
#define MD_GET_SP(_t) *((long *)&((_t)->context[0].__jmpbuf[0]))
#elif defined(__mips__)
/* https://github.com/ossrs/state-threads/issues/21 */
#define MD_USE_BUILTIN_SETJMP
@ -191,7 +195,7 @@
#define MD_USE_BUILTIN_SETJMP
#define MD_GET_SP(_t) *((long *)&((_t)->context[0].__jmpbuf[0]))
#elif defined(__loongarch__)
#elif defined(__loongarch64)
/* https://github.com/ossrs/state-threads/issues/24 */
#define MD_USE_BUILTIN_SETJMP
#define MD_GET_SP(_t) *((long *)&((_t)->context[0].__jmpbuf[0]))

View file

@ -433,9 +433,87 @@
#elif defined(__mips64)
/****************************************************************/
/* For MIPS64, see https://s3-eu-west-1.amazonaws.com/downloads-mips/documents/MIPS_Architecture_MIPS64_InstructionSet_%20AFP_P_MD00087_06.05.pdf */
/*
* Internal __jmp_buf layout
*/
#define JB_SP 0 /* Stack pointer */
#define JB_RA 11 /* Return address */
#define JB_GP 1 /* Global pointer */
#define JB_S0 3 /* S0-S7, Saved temporaries */
#define JB_S1 4 /* S0-S7, Saved temporaries */
#define JB_S2 5 /* S0-S7, Saved temporaries */
#define JB_S3 6 /* S0-S7, Saved temporaries */
#define JB_S4 7 /* S0-S7, Saved temporaries */
#define JB_S5 8 /* S0-S7, Saved temporaries */
#define JB_S6 9 /* S0-S7, Saved temporaries */
#define JB_S7 10 /* S0-S7, Saved temporaries */
#define JB_FP 2 /* FP/S8 Frame pointer */
.file "md_linux.S"
.text
/* _st_md_cxt_save(__jmp_buf env) */ /* The env is $a0, https://en.wikipedia.org/wiki/MIPS_architecture#Calling_conventions */
.globl _st_md_cxt_save
.type _st_md_cxt_save, %function
.align 2
_st_md_cxt_save:
sd $sp, 0($a0) /* Save sp to env[0], *(long*)($a0+ 0) =sp */
sd $ra, 8($a0) /* Save ra to env[1], *(long*)($a0+ 8)=ra, the return address, https://chortle.ccsu.edu/AssemblyTutorial/Chapter-26/ass26_4.html */
sd $gp, 16($a0) /* Save gp to env[2], *(long*)($a0+16) =gp */
sd $s0, 24($a0) /* Save s0 to env[3], *(long*)($a0+24)=s0 */
sd $s1, 32($a0) /* Save s1 to env[4], *(long*)($a0+32)=s1 */
sd $s2, 40($a0) /* Save s2 to env[5], *(long*)($a0+40)=s2 */
sd $s3, 48($a0) /* Save s3 to env[6], *(long*)($a0+48)=s3 */
sd $s4, 56($a0) /* Save s4 to env[7], *(long*)($a0+56)=s4 */
sd $s5, 64($a0) /* Save s5 to env[8], *(long*)($a0+64)=s5 */
sd $s6, 72($a0) /* Save s6 to env[9], *(long*)($a0+72)=s6 */
sd $s7, 80($a0) /* Save s7 to env[10], *(long*)($a0+80)=s7 */
sd $fp, 88($a0) /* Save fp to env[11], *(long*)($a0+88) =fp */
li $v0, 0 /* Set return value to 0 */
jr $ra /* Return */
.size _st_md_cxt_save, .-_st_md_cxt_save
/****************************************************************/
/* _st_md_cxt_restore(__jmp_buf env, int val) */
.globl _st_md_cxt_restore
.type _st_md_cxt_restore, %function
.align 2
_st_md_cxt_restore:
ld $sp, 0($a0) /* Load sp from env[0], sp=*(long*)($a0+ 0) */
ld $ra, 8($a0) /* Load sp from env[1], ra=*(long*)($a0+ 8), the saved return address */
ld $gp, 16($a0) /* Load sp from env[2], gp=*(long*)($a0+16) */
ld $s0, 24($a0) /* Load sp from env[3], s0=*(long*)($a0+24) */
ld $s1, 32($a0) /* Load sp from env[4], s1=*(long*)($a0+32) */
ld $s2, 40($a0) /* Load sp from env[5], s2=*(long*)($a0+40) */
ld $s3, 48($a0) /* Load sp from env[6], s3=*(long*)($a0+48) */
ld $s4, 56($a0) /* Load sp from env[7], s4=*(long*)($a0+56) */
ld $s5, 64($a0) /* Load sp from env[8], s5=*(long*)($a0+64) */
ld $s6, 72($a0) /* Load sp from env[9], s6=*(long*)($a0+72) */
ld $s7, 80($a0) /* Load sp from env[10], s7=*(long*)($a0+80) */
ld $fp, 88($a0) /* Load sp from env[2], fp=*(long*)($a0+88) */
li $v0, 1 /* Set return value to 1 */
jr $ra /* Return to the saved return address */
.size _st_md_cxt_restore, .-_st_md_cxt_restore
/****************************************************************/
#elif defined(__mips__)
/****************************************************************/
/* For MIPS32, see https://s3-eu-west-1.amazonaws.com/downloads-mips/documents/MD00565-2B-MIPS32-QRC-01.01.pdf */
/*
* Internal __jmp_buf layout
@ -513,7 +591,7 @@
#elif defined(__loongarch__)
#elif defined(__loongarch64)
/****************************************************************/

View file

@ -9,7 +9,8 @@ int main(int argc, char** argv)
{
st_init();
for (int i = 0; i < 10000; i++) {
int i;
for (i = 0; i < 10000; i++) {
printf("#%03d, Hello, state-threads world!\n", i);
st_sleep(1);
}

View file

@ -25,11 +25,23 @@ int main(int argc, char** argv)
// https://s3-eu-west-1.amazonaws.com/downloads-mips/documents/MD00565-2B-MIPS32-QRC-01.01.pdf
printf("__mips__: %d, __mips: %d, _MIPSEL: %d\n", __mips__, __mips, _MIPSEL);
#endif
#ifdef __mips64
printf("__mips64: %d\n", __mips64);
#endif
#ifdef __x86_64__
printf("__x86_64__: %d\n", __x86_64__);
#endif
#ifdef __loongarch__
printf("__loongarch__: %d, __loongarch64 :%d\n", __loongarch__, __loongarch64);
#ifdef __loongarch64
printf("__loongarch__: %d __loongarch64: %d\n", __loongarch__, __loongarch64);
#endif
#ifdef __riscv
printf("__riscv: %d\n", __riscv);
#endif
#ifdef __arm__
printf("__arm__: %d\n", __arm__);
#endif
#ifdef __aarch64__
printf("__aarch64__: %d\n", __aarch64__);
#endif
printf("\nCompiler specs:\n");
@ -71,7 +83,52 @@ int foo_return_one_arg1(int r0)
}
#ifdef __linux__
#ifdef __mips__
#if defined(__riscv) || defined(__arm__) || defined(__aarch64__)
void print_jmpbuf() {
}
#elif __mips64
void print_jmpbuf()
{
// https://en.wikipedia.org/wiki/MIPS_architecture#Calling_conventions
register void* ra asm("ra");
register void* gp asm("gp");
register void* sp asm("sp");
register void* fp asm("fp");
// $s0$s7 $16$23 saved temporaries
register void* s0 asm("s0");
register void* s1 asm("s1");
register void* s2 asm("s2");
register void* s3 asm("s3");
register void* s4 asm("s4");
register void* s5 asm("s5");
register void* s6 asm("s6");
register void* s7 asm("s7");
/*
typedef unsigned long long __jmp_buf[13];
typedef struct __jmp_buf_tag {
__jmp_buf __jmpbuf;
int __mask_was_saved;
__sigset_t __saved_mask;
} jmp_buf[1];
*/
jmp_buf ctx = {0};
int r0 = setjmp(ctx);
if (!r0) {
longjmp(ctx, 1);
}
printf("ra=%p, sp=%p, s0=%p, s1=%p, s2=%p, s3=%p, s4=%p, s5=%p, s6=%p, s7=%p, fp=%p, gp=%p\n",
ra, sp, s0, s1, s2, s3, s4, s5, s6, s7, fp, gp);
int nn_jb = sizeof(ctx[0].__jmpbuf);
printf("sizeof(jmp_buf)=%d (unsigned long long [%d])\n", nn_jb, nn_jb/8);
unsigned char* p = (unsigned char*)ctx[0].__jmpbuf;
print_buf(p, nn_jb);
}
#elif __mips__
void print_jmpbuf()
{
// https://en.wikipedia.org/wiki/MIPS_architecture#Calling_conventions
@ -112,7 +169,7 @@ void print_jmpbuf()
unsigned char* p = (unsigned char*)ctx[0].__jb;
print_buf(p, nn_jb);
}
#elif __loongarch__
#elif __loongarch64
void print_jmpbuf()
{
// https://github.com/ossrs/state-threads/issues/24#porting
@ -192,7 +249,8 @@ void print_buf(unsigned char* p, int nn_jb)
{
printf(" ");
for (int i = 0; i < nn_jb; i++) {
int i;
for (i = 0; i < nn_jb; i++) {
printf("0x%02x ", (unsigned char)p[i]);
int newline = ((i + 1) % sizeof(void*));

View file

@ -48,7 +48,7 @@ void verify_jmpbuf()
unsigned char* p = (unsigned char*)ctx[0].__jb;
print_buf(p, nn_jb);
}
#elif __loongarch__
#elif __loongarch64
void verify_jmpbuf()
{
// https://github.com/ossrs/state-threads/issues/24#porting

View file

@ -4,7 +4,7 @@ ST_DIR = ..
# The main dir of st utest.
ST_UTEST = .
# The main dir of gtest.
GTEST_DIR = $(ST_UTEST)/gtest
GTEST_DIR = $(ST_UTEST)/gtest-fit/googletest
# Flags passed to the C++ compiler.
CXXFLAGS += -g -O0 -std=c++11
@ -45,16 +45,29 @@ $(ST_DIR)/obj/gtest.a : $(ST_DIR)/obj/gtest-all.o
ST_UTEST_DEPS = $(ST_DIR)/obj/libst.a
# Depends, utest header files
UTEST_DEPS = $(ST_UTEST)/st_utest.hpp
UTEST_DEPS = $(ST_UTEST)/st_utest.hpp Makefile
# Compile all sources files at current directory. For example:
# (st_utest.cpp st_utest_coroutines.cpp st_utest_tcp.cpp)
SOURCE_FILES = $(shell ls *.cpp)
#
# Convert all source files to object files. For example:
# (st_utest.o st_utest_coroutines.o st_utest_tcp.o)
# https://ftp.gnu.org/old-gnu/Manuals/make-3.79.1/html_chapter/make_8.html
OBJECTS_FILES = $(patsubst %.cpp,%.o,$(SOURCE_FILES))
#
# Prefix object files to objects. For example:
# ($(ST_DIR)/obj/st_utest.o $(ST_DIR)/obj/st_utest_coroutines.o $(ST_DIR)/obj/st_utest_tcp.o)
OBJECTS = $(addprefix $(ST_DIR)/obj/,$(OBJECTS_FILES))
# Objects, build each object of utest
$(ST_DIR)/obj/st_utest.o : st_utest.cpp $(ST_UTEST_DEPS) $(UTEST_DEPS)
$(CXX) -c st_utest.cpp -o $@ \
$(ST_DIR)/obj/%.o : %.cpp $(ST_UTEST_DEPS) $(UTEST_DEPS)
$(CXX) -c $< -o $@ \
$(CXXFLAGS) $(UTEST_FLAGS) \
$(WARNFLAGS) \
-I$(GTEST_DIR)/include -I$(ST_UTEST) -I$(ST_DIR) -I$(ST_DIR)/obj
# generate the utest binary
$(ST_DIR)/obj/st_utest : $(ST_DIR)/obj/st_utest.o $(ST_DIR)/obj/gtest.a $(ST_UTEST_DEPS)
# Generate the utest binary
$(ST_DIR)/obj/st_utest : $(OBJECTS) $(ST_DIR)/obj/gtest.a $(ST_UTEST_DEPS)
$(CXX) -o $@ $(CXXFLAGS) $(UTEST_FLAGS) \
-lpthread -ldl $^

View file

@ -6,6 +6,14 @@
#include <st.h>
#include <assert.h>
std::ostream& operator<<(std::ostream& out, const ErrorObject* err) {
if (!err) return out;
if (err->r0_) out << "r0=" << err->r0_;
if (err->errno_) out << ", errno=" << err->errno_;
if (!err->message_.empty()) out << ", msg=" << err->message_;
return out;
}
// We could do something in the main of utest.
// Copy from gtest-1.6.0/src/gtest_main.cc
GTEST_API_ int main(int argc, char **argv) {
@ -25,7 +33,7 @@ GTEST_API_ int main(int argc, char **argv) {
}
// basic test and samples.
VOID TEST(SampleTest, FastSampleInt64Test)
VOID TEST(SampleTest, ExampleIntSizeTest)
{
EXPECT_EQ(1, (int)sizeof(int8_t));
EXPECT_EQ(2, (int)sizeof(int16_t));
@ -33,107 +41,3 @@ VOID TEST(SampleTest, FastSampleInt64Test)
EXPECT_EQ(8, (int)sizeof(int64_t));
}
void* pfn_coroutine(void* /*arg*/)
{
st_usleep(0);
return NULL;
}
VOID TEST(SampleTest, StartCoroutine)
{
st_thread_t trd = st_thread_create(pfn_coroutine, NULL, 1, 0);
EXPECT_TRUE(trd != NULL);
// Wait for joinable coroutine to quit.
st_thread_join(trd, NULL);
}
VOID TEST(SampleTest, StartCoroutineX3)
{
st_thread_t trd0 = st_thread_create(pfn_coroutine, NULL, 1, 0);
st_thread_t trd1 = st_thread_create(pfn_coroutine, NULL, 1, 0);
st_thread_t trd2 = st_thread_create(pfn_coroutine, NULL, 1, 0);
EXPECT_TRUE(trd0 != NULL && trd1 != NULL && trd2 != NULL);
// Wait for joinable coroutine to quit.
st_thread_join(trd1, NULL);
st_thread_join(trd2, NULL);
st_thread_join(trd0, NULL);
}
void* pfn_coroutine_add(void* arg)
{
int v = 0;
int* pi = (int*)arg;
// Load the change of arg.
while (v != *pi) {
v = *pi;
st_usleep(0);
}
// Add with const.
v += 100;
*pi = v;
return NULL;
}
VOID TEST(SampleTest, StartCoroutineAdd)
{
int v = 0;
st_thread_t trd = st_thread_create(pfn_coroutine_add, &v, 1, 0);
EXPECT_TRUE(trd != NULL);
// Wait for joinable coroutine to quit.
st_thread_join(trd, NULL);
EXPECT_EQ(100, v);
}
VOID TEST(SampleTest, StartCoroutineAddX3)
{
int v = 0;
st_thread_t trd0 = st_thread_create(pfn_coroutine_add, &v, 1, 0);
st_thread_t trd1 = st_thread_create(pfn_coroutine_add, &v, 1, 0);
st_thread_t trd2 = st_thread_create(pfn_coroutine_add, &v, 1, 0);
EXPECT_TRUE(trd0 != NULL && trd1 != NULL && trd2 != NULL);
// Wait for joinable coroutine to quit.
st_thread_join(trd0, NULL);
st_thread_join(trd1, NULL);
st_thread_join(trd2, NULL);
EXPECT_EQ(300, v);
}
int pfn_coroutine_params_x4(int a, int b, int c, int d)
{
int e = 0;
st_usleep(0);
e += a + b + c + d;
e += 100;
return e;
}
void* pfn_coroutine_params(void* arg)
{
int r0 = pfn_coroutine_params_x4(1, 2, 3, 4);
*(int*)arg = r0;
return NULL;
}
VOID TEST(SampleTest, StartCoroutineParams)
{
int r0 = 0;
st_thread_t trd = st_thread_create(pfn_coroutine_params, &r0, 1, 0);
EXPECT_TRUE(trd != NULL);
// Wait for joinable coroutine to quit.
st_thread_join(trd, NULL);
EXPECT_EQ(110, r0);
}

View file

@ -10,7 +10,118 @@
// @see https://stackoverflow.com/questions/47839718/sstream-redeclared-with-public-access-compiler-error
#include <gtest/gtest.h>
#include <st.h>
#include <string>
#define VOID
// Close the fd automatically.
#define StFdCleanup(fd, stfd) impl__StFdCleanup _ST_free_##fd(&fd, &stfd)
#define StStfdCleanup(stfd) impl__StFdCleanup _ST_free_##stfd(NULL, &stfd)
class impl__StFdCleanup {
int* fd_;
st_netfd_t* stfd_;
public:
impl__StFdCleanup(int* fd, st_netfd_t* stfd) : fd_(fd), stfd_(stfd) {
}
virtual ~impl__StFdCleanup() {
if (stfd_ && *stfd_) {
st_netfd_close(*stfd_);
} else if (fd_ && *fd_ > 0) {
::close(*fd_);
}
}
};
// For coroutine function to return with error object.
struct ErrorObject {
int r0_;
int errno_;
std::string message_;
ErrorObject(int r0, std::string message) : r0_(r0), errno_(errno), message_(message) {
}
};
extern std::ostream& operator<<(std::ostream& out, const ErrorObject* err);
#define ST_ASSERT_ERROR(error, r0, message) if (error) return new ErrorObject(r0, message)
#define ST_COROUTINE_JOIN(trd, r0) ErrorObject* r0 = NULL; SrsAutoFree(ErrorObject, r0); if (trd) st_thread_join(trd, (void**)&r0)
#define ST_EXPECT_SUCCESS(r0) EXPECT_TRUE(!r0) << r0
#define ST_EXPECT_FAILED(r0) EXPECT_TRUE(r0) << r0
#include <stdlib.h>
// To free the instance in the current scope, for instance, MyClass* ptr,
// which is a ptr and this class will:
// 1. free the ptr.
// 2. set ptr to NULL.
//
// Usage:
// MyClass* po = new MyClass();
// // ...... use po
// SrsAutoFree(MyClass, po);
//
// Usage for array:
// MyClass** pa = new MyClass*[size];
// // ....... use pa
// SrsAutoFreeA(MyClass*, pa);
//
// @remark the MyClass can be basic type, for instance, SrsAutoFreeA(char, pstr),
// where the char* pstr = new char[size].
// To delete object.
#define SrsAutoFree(className, instance) \
impl_SrsAutoFree<className> _auto_free_##instance(&instance, false, false, NULL)
// To delete array.
#define SrsAutoFreeA(className, instance) \
impl_SrsAutoFree<className> _auto_free_array_##instance(&instance, true, false, NULL)
// Use free instead of delete.
#define SrsAutoFreeF(className, instance) \
impl_SrsAutoFree<className> _auto_free_##instance(&instance, false, true, NULL)
// Use hook instead of delete.
#define SrsAutoFreeH(className, instance, hook) \
impl_SrsAutoFree<className> _auto_free_##instance(&instance, false, false, hook)
// The template implementation.
template<class T>
class impl_SrsAutoFree
{
private:
T** ptr;
bool is_array;
bool _use_free;
void (*_hook)(T*);
public:
// If use_free, use free(void*) to release the p.
// If specified hook, use hook(p) to release it.
// Use delete to release p, or delete[] if p is an array.
impl_SrsAutoFree(T** p, bool array, bool use_free, void (*hook)(T*)) {
ptr = p;
is_array = array;
_use_free = use_free;
_hook = hook;
}
virtual ~impl_SrsAutoFree() {
if (ptr == NULL || *ptr == NULL) {
return;
}
if (_use_free) {
free(*ptr);
} else if (_hook) {
_hook(*ptr);
} else {
if (is_array) {
delete[] *ptr;
} else {
delete *ptr;
}
}
*ptr = NULL;
}
};
// The time unit in ms, for example 100 * SRS_UTIME_MILLISECONDS means 100ms.
#define SRS_UTIME_MILLISECONDS 1000
#endif

View file

@ -0,0 +1,120 @@
/* SPDX-License-Identifier: MIT */
/* Copyright (c) 2021 Winlin */
#include <st_utest.hpp>
#include <st.h>
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The utest for empty coroutine.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void* coroutine(void* /*arg*/)
{
st_usleep(0);
return NULL;
}
VOID TEST(CoroutineTest, StartCoroutine)
{
st_thread_t trd = st_thread_create(coroutine, NULL, 1, 0);
EXPECT_TRUE(trd != NULL);
// Wait for joinable coroutine to quit.
st_thread_join(trd, NULL);
}
VOID TEST(CoroutineTest, StartCoroutineX3)
{
st_thread_t trd0 = st_thread_create(coroutine, NULL, 1, 0);
st_thread_t trd1 = st_thread_create(coroutine, NULL, 1, 0);
st_thread_t trd2 = st_thread_create(coroutine, NULL, 1, 0);
EXPECT_TRUE(trd0 != NULL && trd1 != NULL && trd2 != NULL);
// Wait for joinable coroutine to quit.
st_thread_join(trd1, NULL);
st_thread_join(trd2, NULL);
st_thread_join(trd0, NULL);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The utest for adding coroutine.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void* coroutine_add(void* arg)
{
int v = 0;
int* pi = (int*)arg;
// Load the change of arg.
while (v != *pi) {
v = *pi;
st_usleep(0);
}
// Add with const.
v += 100;
*pi = v;
return NULL;
}
VOID TEST(CoroutineTest, StartCoroutineAdd)
{
int v = 0;
st_thread_t trd = st_thread_create(coroutine_add, &v, 1, 0);
EXPECT_TRUE(trd != NULL);
// Wait for joinable coroutine to quit.
st_thread_join(trd, NULL);
EXPECT_EQ(100, v);
}
VOID TEST(CoroutineTest, StartCoroutineAddX3)
{
int v = 0;
st_thread_t trd0 = st_thread_create(coroutine_add, &v, 1, 0);
st_thread_t trd1 = st_thread_create(coroutine_add, &v, 1, 0);
st_thread_t trd2 = st_thread_create(coroutine_add, &v, 1, 0);
EXPECT_TRUE(trd0 != NULL && trd1 != NULL && trd2 != NULL);
// Wait for joinable coroutine to quit.
st_thread_join(trd0, NULL);
st_thread_join(trd1, NULL);
st_thread_join(trd2, NULL);
EXPECT_EQ(300, v);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The utest for output params coroutine.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int coroutine_params_x4(int a, int b, int c, int d)
{
int e = 0;
st_usleep(0);
e += a + b + c + d;
e += 100;
return e;
}
void* coroutine_params(void* arg)
{
int r0 = coroutine_params_x4(1, 2, 3, 4);
*(int*)arg = r0;
return NULL;
}
VOID TEST(CoroutineTest, StartCoroutineParams)
{
int r0 = 0;
st_thread_t trd = st_thread_create(coroutine_params, &r0, 1, 0);
EXPECT_TRUE(trd != NULL);
// Wait for joinable coroutine to quit.
st_thread_join(trd, NULL);
EXPECT_EQ(110, r0);
}

View file

@ -0,0 +1,92 @@
/* SPDX-License-Identifier: MIT */
/* Copyright (c) 2021 Winlin */
#include <st_utest.hpp>
#include <st.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define ST_UTEST_PORT 26878
#define ST_UTEST_TIMEOUT (100 * SRS_UTIME_MILLISECONDS)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The utest for ping-pong TCP server coroutine.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void* tcp_server(void* /*arg*/)
{
int fd = -1;
st_netfd_t stfd = NULL;
StFdCleanup(fd, stfd);
fd = socket(AF_INET, SOCK_STREAM, 0);
ST_ASSERT_ERROR(fd == -1, fd, "Create socket");
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(ST_UTEST_PORT);
int v = 1;
int r0 = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(int));
ST_ASSERT_ERROR(r0, r0, "Set SO_REUSEADDR");
r0 = ::bind(fd, (const sockaddr*)&addr, sizeof(addr));
ST_ASSERT_ERROR(r0, r0, "Bind socket");
r0 = ::listen(fd, 10);
ST_ASSERT_ERROR(r0, r0, "Listen socket");
stfd = st_netfd_open_socket(fd);
ST_ASSERT_ERROR(!stfd, fd, "Open ST socket");
st_netfd_t client = NULL;
StStfdCleanup(client);
client = st_accept(stfd, NULL, NULL, ST_UTEST_TIMEOUT);
ST_ASSERT_ERROR(!client, fd, "Accept client");
return NULL;
}
void* tcp_client(void* /*arg*/)
{
int fd = -1;
st_netfd_t stfd = NULL;
StFdCleanup(fd, stfd);
fd = socket(AF_INET, SOCK_STREAM, 0);
ST_ASSERT_ERROR(fd == -1, fd, "Create socket");
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(ST_UTEST_PORT);
stfd = st_netfd_open_socket(fd);
ST_ASSERT_ERROR(!stfd, fd, "Open ST socket");
int r0 = st_connect(stfd, (const sockaddr*)&addr, sizeof(addr), ST_UTEST_TIMEOUT);
ST_ASSERT_ERROR(r0, r0, "Connect to server");
return NULL;
}
VOID TEST(TcpTest, TcpConnection)
{
st_thread_t svr = st_thread_create(tcp_server, NULL, 1, 0);
EXPECT_TRUE(svr != NULL);
st_thread_t client = st_thread_create(tcp_client, NULL, 1, 0);
EXPECT_TRUE(client != NULL);
ST_COROUTINE_JOIN(svr, r0);
ST_COROUTINE_JOIN(client, r1);
ST_EXPECT_SUCCESS(r0);
ST_EXPECT_SUCCESS(r1);
}

View file

@ -6,6 +6,7 @@ The changelog for SRS.
## SRS 5.0 Changelog
* v5.0, 2022-08-06, Support MIPS 64bits for loongson 3A4000/3B3000. v5.0.34
* v5.0, 2022-06-29, Support multiple threads by thread pool. v5.0.32
* v5.0, 2022-06-28, ST: Support thread-local for multiple threads. v5.0.31
* v5.0, 2022-06-17, Merge [#3010](https://github.com/ossrs/srs/pull/3010): SRT: Support Coroutine Native SRT over ST. (#3010). v5.0.30

View file

@ -9,6 +9,6 @@
#define VERSION_MAJOR 5
#define VERSION_MINOR 0
#define VERSION_REVISION 33
#define VERSION_REVISION 34
#endif