diff options
-rw-r--r-- | erts/emulator/nifs/common/socket_int.h | 8 | ||||
-rw-r--r-- | erts/emulator/nifs/common/socket_nif.c | 587 | ||||
-rw-r--r-- | erts/emulator/nifs/common/socket_util.c | 2 | ||||
-rw-r--r-- | lib/kernel/test/socket_SUITE.erl | 4227 |
4 files changed, 4037 insertions, 787 deletions
diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index 06f677482c..7d223b8259 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -339,8 +339,12 @@ extern ERL_NIF_TERM esock_atom_einval; #define MLOCK(M) enif_mutex_lock((M)) #define MUNLOCK(M) enif_mutex_unlock((M)) -#define MONP(E,D,P,M) enif_monitor_process((E), (D), (P), (M)) -#define DEMONP(E,D,M) enif_demonitor_process((E), (D), (M)) +// #define MONP(S,E,D,P,M) enif_monitor_process((E), (D), (P), (M)) +// #define DEMONP(S,E,D,M) enif_demonitor_process((E), (D), (M)) +#define MONP(S,E,D,P,M) esock_monitor((S), (E), (D), (P), (M)) +#define DEMONP(S,E,D,M) esock_demonitor((S), (E), (D), (M)) +#define MON_INIT(M) esock_monitor_init((M)) +// #define MON_COMP(M1, M2) esock_monitor_compare((M1), (M2)) #define SELECT(E,FD,M,O,P,R) \ if (enif_select((E), (FD), (M), (O), (P), (R)) < 0) \ diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 4544604f28..a137692d68 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -394,7 +394,18 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; #define SOCKET_STATE_ACCEPTING (SOCKET_STATE_LISTENING | SOCKET_FLAG_ACC) #define SOCKET_STATE_CLOSING (SOCKET_FLAG_CLOSE) -#define IS_OPEN(d) \ +#define IS_CLOSED(d) \ + ((d)->state == SOCKET_STATE_CLOSED) + +/* +#define IS_STATE(d, f) \ + (((d)->state & (f)) == (f)) +*/ + +#define IS_CLOSING(d) \ + (((d)->state & SOCKET_STATE_CLOSING) == SOCKET_STATE_CLOSING) + +#define IS_OPEN(d) \ (((d)->state & SOCKET_FLAG_OPEN) == SOCKET_FLAG_OPEN) #define IS_CONNECTED(d) \ @@ -723,9 +734,16 @@ static unsigned long one_value = 1; ((sap)->in4.sin_port) : -1) +typedef union { + ErlNifMonitor mon; + uint32_t raw[4]; +} ESockMonitor; + + typedef struct { ErlNifPid pid; // PID of the requesting process - ErlNifMonitor mon; // Monitor to the requesting process + // ErlNifMonitor mon; Monitor to the requesting process + ESockMonitor mon; // Monitor to the requesting process ERL_NIF_TERM ref; // The (unique) reference (ID) of the request } SocketRequestor; @@ -758,7 +776,8 @@ typedef struct { /* +++ Controller (owner) process +++ */ ErlNifPid ctrlPid; - ErlNifMonitor ctrlMon; + // ErlNifMonitor ctrlMon; + ESockMonitor ctrlMon; /* +++ Write stuff +++ */ ErlNifMutex* writeMtx; @@ -801,7 +820,8 @@ typedef struct { /* +++ Close stuff +++ */ ErlNifMutex* closeMtx; ErlNifPid closerPid; - ErlNifMonitor closerMon; + // ErlNifMonitor closerMon; + ESockMonitor closerMon; ERL_NIF_TERM closeRef; BOOLEAN_T closeLocal; @@ -2196,7 +2216,8 @@ static ERL_NIF_TERM acceptor_push(ErlNifEnv* env, static BOOLEAN_T acceptor_pop(ErlNifEnv* env, SocketDescriptor* descP, ErlNifPid* pid, - ErlNifMonitor* mon, + // ErlNifMonitor* mon, + ESockMonitor* mon, ERL_NIF_TERM* ref); static BOOLEAN_T acceptor_unqueue(ErlNifEnv* env, SocketDescriptor* descP, @@ -2212,7 +2233,8 @@ static ERL_NIF_TERM writer_push(ErlNifEnv* env, static BOOLEAN_T writer_pop(ErlNifEnv* env, SocketDescriptor* descP, ErlNifPid* pid, - ErlNifMonitor* mon, + // ErlNifMonitor* mon, + ESockMonitor* mon, ERL_NIF_TERM* ref); static BOOLEAN_T writer_unqueue(ErlNifEnv* env, SocketDescriptor* descP, @@ -2228,7 +2250,8 @@ static ERL_NIF_TERM reader_push(ErlNifEnv* env, static BOOLEAN_T reader_pop(ErlNifEnv* env, SocketDescriptor* descP, ErlNifPid* pid, - ErlNifMonitor* mon, + // ErlNifMonitor* mon, + ESockMonitor* mon, ERL_NIF_TERM* ref); static BOOLEAN_T reader_unqueue(ErlNifEnv* env, SocketDescriptor* descP, @@ -2241,8 +2264,27 @@ static void qpush(SocketRequestQueue* q, SocketRequestQueueElement* e); static SocketRequestQueueElement* qpop(SocketRequestQueue* q); static BOOLEAN_T qunqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const char* slogan, SocketRequestQueue* q, const ErlNifPid* pid); + +static int esock_monitor(const char* slogan, + ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid, + ESockMonitor* mon); +static int esock_demonitor(const char* slogan, + ErlNifEnv* env, + SocketDescriptor* descP, + ESockMonitor* monP); +static void esock_monitor_init(ESockMonitor* mon); +/* +static int esock_monitor_compare(const ErlNifMonitor* mon1, + const ESockMonitor* mon2); +*/ + + /* #if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) static size_t my_strnlen(const char *s, size_t maxlen); @@ -2293,6 +2335,7 @@ static BOOLEAN_T extract_iow(ErlNifEnv* env, static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); + #if HAVE_IN6 # if ! defined(HAVE_IN6ADDR_ANY) || ! HAVE_IN6ADDR_ANY # if HAVE_DECL_IN6ADDR_ANY_INIT @@ -4193,7 +4236,6 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, res = enif_make_resource(env, descP); enif_release_resource(descP); // We should really store a reference ... - /* Keep track of the creator * This should not be a problem but just in case * the *open* function is used with the wrong kind @@ -4202,9 +4244,10 @@ ERL_NIF_TERM nopen(ErlNifEnv* env, if (enif_self(env, &descP->ctrlPid) == NULL) return esock_make_error(env, atom_exself); - if (MONP(env, descP, + if (MONP("nopen -> ctrl", + env, descP, &descP->ctrlPid, - &descP->ctrlMon) > 0) + &descP->ctrlMon) != 0) return esock_make_error(env, atom_exmon); @@ -4363,6 +4406,9 @@ ERL_NIF_TERM nif_bind(ErlNifEnv* env, } eSockAddr = argv[1]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_bind -> args when sock = %d (0x%lX)" "\r\n Socket: %T" @@ -4452,6 +4498,9 @@ ERL_NIF_TERM nif_connect(ErlNifEnv* env, } eSockAddr = argv[1]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_connect -> args when sock = %d:" "\r\n Socket: %T" @@ -4653,6 +4702,9 @@ ERL_NIF_TERM nif_listen(ErlNifEnv* env, return enif_make_badarg(env); } + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_listen -> args when sock = %d:" "\r\n Socket: %T" @@ -4714,6 +4766,9 @@ ERL_NIF_TERM nif_accept(ErlNifEnv* env, } ref = argv[1]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_accept -> args when sock = %d:" "\r\n Socket: %T" @@ -4791,9 +4846,10 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "naccept_listening -> would block\r\n") ); descP->currentAcceptor.pid = caller; - if (MONP(env, descP, + if (MONP("naccept_listening -> current acceptor", + env, descP, &descP->currentAcceptor.pid, - &descP->currentAcceptor.mon) > 0) + &descP->currentAcceptor.mon) != 0) return esock_make_error(env, atom_exmon); descP->currentAcceptor.ref = enif_make_copy(descP->env, ref); @@ -4868,9 +4924,10 @@ ERL_NIF_TERM naccept_listening(ErlNifEnv* env, enif_release_resource(accDescP); // We should really store a reference ... accDescP->ctrlPid = caller; - if (MONP(env, accDescP, + if (MONP("naccept_listening -> ctrl", + env, accDescP, &accDescP->ctrlPid, - &accDescP->ctrlMon) > 0) { + &accDescP->ctrlMon) != 0) { sock_close(accSock); return esock_make_error(env, atom_exmon); } @@ -4984,7 +5041,8 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "naccept_accepting -> accept success\r\n") ); - DEMONP(env, descP, &descP->currentAcceptor.mon); + DEMONP("naccept_accepting -> current acceptor", + env, descP, &descP->currentAcceptor.mon); if ((accEvent = sock_create_event(accSock)) == INVALID_EVENT) { save_errno = sock_errno(); @@ -5006,9 +5064,10 @@ ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, enif_release_resource(accDescP); // We should really store a reference ... accDescP->ctrlPid = caller; - if (MONP(env, accDescP, + if (MONP("naccept_accepting -> ctrl", + env, accDescP, &accDescP->ctrlPid, - &accDescP->ctrlMon) > 0) { + &accDescP->ctrlMon) != 0) { sock_close(accSock); return esock_make_error(env, atom_exmon); } @@ -5094,6 +5153,9 @@ ERL_NIF_TERM nif_send(ErlNifEnv* env, } sendRef = argv[1]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_send -> args when sock = %d:" "\r\n Socket: %T" @@ -5218,6 +5280,9 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, sendRef = argv[1]; eSockAddr = argv[3]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_sendto -> args when sock = %d:" "\r\n Socket: %T" @@ -5341,6 +5406,9 @@ ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env, sendRef = argv[1]; eMsgHdr = argv[2]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_sendmsg -> args when sock = %d:" "\r\n Socket: %T" @@ -5630,6 +5698,9 @@ ERL_NIF_TERM nif_recv(ErlNifEnv* env, } recvRef = argv[1]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + if (!IS_CONNECTED(descP)) return esock_make_error(env, atom_enotconn); @@ -5769,6 +5840,9 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, } recvRef = argv[1]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_recvfrom -> args when sock = %d:" "\r\n Socket: %T" @@ -5929,6 +6003,9 @@ ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env, } recvRef = argv[1]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_recvmsg -> args when sock = %d:" "\r\n Socket: %T" @@ -6087,6 +6164,9 @@ ERL_NIF_TERM nif_close(ErlNifEnv* env, return enif_make_badarg(env); } + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + return nclose(env, descP); } @@ -6102,7 +6182,11 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, int type = descP->type; int protocol = descP->protocol; - SSDBG( descP, ("SOCKET", "nclose -> [%d] entry\r\n", descP->sock) ); + SSDBG( descP, ("SOCKET", "nclose -> [%d] entry (0x%lX, 0x%lX, 0x%lX)\r\n", + descP->sock, + descP->currentWriterP, + descP->currentReaderP, + descP->currentAcceptorP) ); MLOCK(descP->closeMtx); @@ -6127,11 +6211,18 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, /* Monitor the caller, since we should complete this operation even if * the caller dies (for whatever reason). + * + * <KOLLA> + * + * Can we actiually use this for anything? + * + * </KOLLA> */ - if (MONP(env, descP, - &descP->closerPid, - &descP->closerMon) > 0) { + if (MONP("nclose -> closer", + env, descP, + &descP->closerPid, + &descP->closerMon) != 0) { MUNLOCK(descP->closeMtx); return esock_make_error(env, atom_exmon); } @@ -6150,17 +6241,24 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, if (selectRes & ERL_NIF_SELECT_STOP_CALLED) { /* Prep done - inform the caller it can finalize (close) directly */ SSDBG( descP, - ("SOCKET", "nclose -> [%d] stop called\r\n", descP->sock) ); + ("SOCKET", "nclose -> [%d] stop was called\r\n", descP->sock) ); dec_socket(domain, type, protocol); + DEMONP("nclose -> closer", env, descP, &descP->closerMon); reply = esock_atom_ok; } else if (selectRes & ERL_NIF_SELECT_STOP_SCHEDULED) { /* The stop callback function has been *scheduled* which means that we * have to wait for it to complete. */ SSDBG( descP, - ("SOCKET", "nclose -> [%d] stop scheduled\r\n", descP->sock) ); + ("SOCKET", "nclose -> [%d] stop was scheduled\r\n", + descP->sock) ); dec_socket(domain, type, protocol); // SHALL WE DO THIS AT finalize? reply = esock_make_ok2(env, descP->closeRef); } else { + + SSDBG( descP, + ("SOCKET", "nclose -> [%d] stop failed: %d\r\n", + descP->sock, selectRes) ); + /* <KOLLA> * * WE SHOULD REALLY HAVE A WAY TO CLOBBER THE SOCKET, @@ -6170,6 +6268,10 @@ ERL_NIF_TERM nclose(ErlNifEnv* env, * * </KOLLA> */ + + // No point in having this? + DEMONP("nclose -> closer", env, descP, &descP->closerMon); + reason = MKT2(env, atom_select, MKI(env, selectRes)); reply = esock_make_error(env, reason); } @@ -6225,10 +6327,10 @@ ERL_NIF_TERM nfinalize_close(ErlNifEnv* env, { ERL_NIF_TERM reply; - if (descP->state == SOCKET_STATE_CLOSED) + if (IS_CLOSED(descP)) return esock_atom_ok; - if (descP->state != SOCKET_STATE_CLOSING) + if (!IS_CLOSING(descP)) return esock_make_error(env, atom_enotclosing); /* This nif is executed in a dirty scheduler just so that @@ -6291,6 +6393,9 @@ ERL_NIF_TERM nif_shutdown(ErlNifEnv* env, return enif_make_badarg(env); } + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + if (!ehow2how(ehow, &how)) return enif_make_badarg(env); @@ -6375,6 +6480,9 @@ ERL_NIF_TERM nif_setopt(ErlNifEnv* env, eIsEncoded = argv[1]; eVal = argv[4]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + isEncoded = esock_decode_bool(eIsEncoded); /* SGDBG( ("SOCKET", "nif_setopt -> eIsDecoded (%T) decoded: %d\r\n", */ @@ -6521,7 +6629,8 @@ ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env, ERL_NIF_TERM eVal) { ErlNifPid caller, newCtrlPid; - ErlNifMonitor newCtrlMon; + // ErlNifMonitor newCtrlMon; + ESockMonitor newCtrlMon; int xres; SSDBG( descP, @@ -6544,20 +6653,22 @@ ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env, return esock_make_error(env, esock_atom_einval); } - if ((xres = MONP(env, descP, &newCtrlPid, &newCtrlMon)) != 0) { + if ((xres = MONP("nsetopt_otp_ctrl_proc -> (new) ctrl", + env, descP, &newCtrlPid, &newCtrlMon)) != 0) { esock_warning_msg("Failed monitor %d) (new) controlling process\r\n", xres); return esock_make_error(env, esock_atom_einval); } - if ((xres = DEMONP(env, descP, &descP->ctrlMon)) != 0) { + if ((xres = DEMONP("nsetopt_otp_ctrl_proc -> (old) ctrl", + env, descP, &descP->ctrlMon)) != 0) { esock_warning_msg("Failed demonitor (%d) " "old controlling process %T (%T)\r\n", xres, descP->ctrlPid, descP->ctrlMon); } descP->ctrlPid = newCtrlPid; - descP->ctrlMon = newCtrlMon; - + descP->ctrlMon = newCtrlMon; + SSDBG( descP, ("SOCKET", "nsetopt_otp_ctrl_proc -> done\r\n") ); return esock_atom_ok; @@ -9640,6 +9751,9 @@ ERL_NIF_TERM nif_getopt(ErlNifEnv* env, eIsEncoded = argv[1]; eOpt = argv[3]; // Is "normally" an int, but if raw mode: {Int, ValueSz} + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_getopt -> args when sock = %d:" "\r\n Socket: %T" @@ -12237,6 +12351,9 @@ ERL_NIF_TERM nif_sockname(ErlNifEnv* env, return enif_make_badarg(env); } + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_sockname -> args when sock = %d:" "\r\n Socket: %T" @@ -12302,6 +12419,9 @@ ERL_NIF_TERM nif_peername(ErlNifEnv* env, return enif_make_badarg(env); } + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_peername -> args when sock = %d:" "\r\n Socket: %T" @@ -12370,6 +12490,9 @@ ERL_NIF_TERM nif_cancel(ErlNifEnv* env, op = argv[1]; opRef = argv[2]; + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + SSDBG( descP, ("SOCKET", "nif_cancel -> args when sock = %d:" "\r\n op: %T" @@ -12498,6 +12621,8 @@ ERL_NIF_TERM ncancel_accept_current(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "ncancel_accept_current -> entry\r\n") ); + DEMONP("ncancel_accept_current -> current acceptor", + env, descP, &descP->currentAcceptor.mon); res = ncancel_read_select(env, descP, descP->currentAcceptor.ref); SSDBG( descP, ("SOCKET", "ncancel_accept_current -> cancel res: %T\r\n", res) ); @@ -12616,6 +12741,8 @@ ERL_NIF_TERM ncancel_send_current(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "ncancel_send_current -> entry\r\n") ); + DEMONP("ncancel_recv_current -> current writer", + env, descP, &descP->currentWriter.mon); res = ncancel_write_select(env, descP, descP->currentWriter.ref); SSDBG( descP, ("SOCKET", "ncancel_send_current -> cancel res: %T\r\n", res) ); @@ -12732,6 +12859,8 @@ ERL_NIF_TERM ncancel_recv_current(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "ncancel_recv_current -> entry\r\n") ); + DEMONP("ncancel_recv_current -> current reader", + env, descP, &descP->currentReader.mon); res = ncancel_read_select(env, descP, descP->currentReader.ref); SSDBG( descP, ("SOCKET", "ncancel_recv_current -> cancel res: %T\r\n", res) ); @@ -12929,7 +13058,8 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, cnt_inc(&descP->writePkgCnt, 1); cnt_inc(&descP->writeByteCnt, written); if (descP->currentWriterP != NULL) - DEMONP(env, descP, &descP->currentWriter.mon); + DEMONP("send_check_result -> current writer", + env, descP, &descP->currentWriter.mon); SSDBG( descP, ("SOCKET", "send_check_result -> " @@ -12968,7 +13098,8 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, if ((saveErrno != EAGAIN) && (saveErrno != EINTR)) { ErlNifPid pid; - ErlNifMonitor mon; + // ErlNifMonitor mon; + ESockMonitor mon; ERL_NIF_TERM ref, res; /* @@ -12983,13 +13114,15 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, res = esock_make_error_errno(env, saveErrno); if (descP->currentWriterP != NULL) { - DEMONP(env, descP, &descP->currentWriter.mon); + DEMONP("send_check_result -> current writer", + env, descP, &descP->currentWriter.mon); while (writer_pop(env, descP, &pid, &mon, &ref)) { SSDBG( descP, ("SOCKET", "send_check_result -> abort %T\r\n", pid) ); send_msg_nif_abort(env, ref, res, &pid); - DEMONP(env, descP, &mon); + DEMONP("send_check_result -> pop'ed writer", + env, descP, &mon); } } @@ -13021,9 +13154,10 @@ ERL_NIF_TERM send_check_result(ErlNifEnv* env, if (enif_self(env, &caller) == NULL) return esock_make_error(env, atom_exself); descP->currentWriter.pid = caller; - if (MONP(env, descP, + if (MONP("send_check_result -> current writer", + env, descP, &descP->currentWriter.pid, - &descP->currentWriter.mon) > 0) + &descP->currentWriter.mon) != 0) return esock_make_error(env, atom_exmon); descP->currentWriter.ref = enif_make_copy(descP->env, sendRef); descP->currentWriterP = &descP->currentWriter; @@ -13108,9 +13242,10 @@ char* recv_init_current_reader(ErlNifEnv* env, return str_exself; descP->currentReader.pid = caller; - if (MONP(env, descP, + if (MONP("recv_init_current_reader -> current reader", + env, descP, &descP->currentReader.pid, - &descP->currentReader.mon) > 0) { + &descP->currentReader.mon) != 0) { return str_exmon; } descP->currentReader.ref = enif_make_copy(descP->env, recvRef); @@ -13135,7 +13270,8 @@ ERL_NIF_TERM recv_update_current_reader(ErlNifEnv* env, { if (descP->currentReaderP != NULL) { - DEMONP(env, descP, &descP->currentReader.mon); + DEMONP("recv_update_current_reader -> current reader", + env, descP, &descP->currentReader.mon); if (reader_pop(env, descP, &descP->currentReader.pid, @@ -13183,16 +13319,19 @@ void recv_error_current_reader(ErlNifEnv* env, { if (descP->currentReaderP != NULL) { ErlNifPid pid; - ErlNifMonitor mon; + // ErlNifMonitor mon; + ESockMonitor mon; ERL_NIF_TERM ref; - DEMONP(env, descP, &descP->currentReader.mon); + DEMONP("recv_error_current_reader -> current reader", + env, descP, &descP->currentReader.mon); while (reader_pop(env, descP, &pid, &mon, &ref)) { SSDBG( descP, ("SOCKET", "recv_error_current_reader -> abort %T\r\n", pid) ); send_msg_nif_abort(env, ref, reason, &pid); - DEMONP(env, descP, &mon); + DEMONP("recv_error_current_reader -> pop'ed reader", + env, descP, &mon); } } } @@ -13368,6 +13507,9 @@ ERL_NIF_TERM recv_check_result(ErlNifEnv* env, SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] eagain\r\n", toRead) ); + if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL) + return esock_make_error_str(env, xres); + SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), descP, NULL, recvRef); @@ -13622,9 +13764,13 @@ ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, } else if ((saveErrno == ERRNO_BLOCK) || (saveErrno == EAGAIN)) { + char* xres; SSDBG( descP, ("SOCKET", "recvmsg_check_result -> eagain\r\n") ); + if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL) + return esock_make_error_str(env, xres); + SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), descP, NULL, recvRef); @@ -15353,6 +15499,11 @@ SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) descP->sock = sock; descP->event = event; + MON_INIT(&descP->currentWriter.mon); + MON_INIT(&descP->currentReader.mon); + MON_INIT(&descP->currentAcceptor.mon); + MON_INIT(&descP->ctrlMon); + MON_INIT(&descP->closerMon); } return descP; @@ -15971,7 +16122,8 @@ ERL_NIF_TERM acceptor_push(ErlNifEnv* env, reqP->pid = pid; reqP->ref = enif_make_copy(descP->env, ref); - if (MONP(env, descP, &pid, &reqP->mon) > 0) { + if (MONP("acceptor_push -> acceptor request", + env, descP, &pid, &reqP->mon) != 0) { FREE(reqP); return esock_make_error(env, atom_exmon); } @@ -15991,7 +16143,8 @@ static BOOLEAN_T acceptor_pop(ErlNifEnv* env, SocketDescriptor* descP, ErlNifPid* pid, - ErlNifMonitor* mon, + // ErlNifMonitor* mon, + ESockMonitor* mon, ERL_NIF_TERM* ref) { SocketRequestQueueElement* e = qpop(&descP->acceptorsQ); @@ -16022,7 +16175,8 @@ BOOLEAN_T acceptor_unqueue(ErlNifEnv* env, SocketDescriptor* descP, const ErlNifPid* pid) { - return qunqueue(env, &descP->acceptorsQ, pid); + return qunqueue(env, descP, "qunqueue -> waiting acceptor", + &descP->acceptorsQ, pid); } @@ -16057,7 +16211,8 @@ ERL_NIF_TERM writer_push(ErlNifEnv* env, reqP->pid = pid; reqP->ref = enif_make_copy(descP->env, ref); - if (MONP(env, descP, &pid, &reqP->mon) > 0) { + if (MONP("writer_push -> writer request", + env, descP, &pid, &reqP->mon) != 0) { FREE(reqP); return esock_make_error(env, atom_exmon); } @@ -16077,7 +16232,8 @@ static BOOLEAN_T writer_pop(ErlNifEnv* env, SocketDescriptor* descP, ErlNifPid* pid, - ErlNifMonitor* mon, + // ErlNifMonitor* mon, + ESockMonitor* mon, ERL_NIF_TERM* ref) { SocketRequestQueueElement* e = qpop(&descP->writersQ); @@ -16108,7 +16264,8 @@ BOOLEAN_T writer_unqueue(ErlNifEnv* env, SocketDescriptor* descP, const ErlNifPid* pid) { - return qunqueue(env, &descP->writersQ, pid); + return qunqueue(env, descP, "qunqueue -> waiting writer", + &descP->writersQ, pid); } @@ -16143,7 +16300,8 @@ ERL_NIF_TERM reader_push(ErlNifEnv* env, reqP->pid = pid; reqP->ref = enif_make_copy(descP->env, ref); - if (MONP(env, descP, &pid, &reqP->mon) > 0) { + if (MONP("reader_push -> reader request", + env, descP, &pid, &reqP->mon) != 0) { FREE(reqP); return esock_make_error(env, atom_exmon); } @@ -16163,7 +16321,8 @@ static BOOLEAN_T reader_pop(ErlNifEnv* env, SocketDescriptor* descP, ErlNifPid* pid, - ErlNifMonitor* mon, + // ErlNifMonitor* mon, + ESockMonitor* mon, ERL_NIF_TERM* ref) { SocketRequestQueueElement* e = qpop(&descP->readersQ); @@ -16194,7 +16353,8 @@ BOOLEAN_T reader_unqueue(ErlNifEnv* env, SocketDescriptor* descP, const ErlNifPid* pid) { - return qunqueue(env, &descP->readersQ, pid); + return qunqueue(env, descP, "qunqueue -> waiting reader", + &descP->readersQ, pid); } @@ -16258,6 +16418,8 @@ SocketRequestQueueElement* qpop(SocketRequestQueue* q) static BOOLEAN_T qunqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const char* slogan, SocketRequestQueue* q, const ErlNifPid* pid) { @@ -16270,6 +16432,8 @@ BOOLEAN_T qunqueue(ErlNifEnv* env, /* We have a match */ + DEMONP(slogan, env, descP, &e->data.mon); + if (p != NULL) { /* Not the first, but could be the last */ if (q->last == e) { @@ -16344,6 +16508,97 @@ void cnt_dec(uint32_t* cnt, uint32_t dec) /* ---------------------------------------------------------------------- + * M o n i t o r W r a p p e r F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +static +int esock_monitor(const char* slogan, + ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid, + ESockMonitor* monP) +{ + int res; + + SSDBG( descP, ("SOCKET", "[%d] %s: try monitor", descP->sock, slogan) ); + // esock_dbg_printf("MONP", "[%d] %s\r\n", descP->sock, slogan); + res = enif_monitor_process(env, descP, pid, &monP->mon); + + if (res != 0) { + SSDBG( descP, ("SOCKET", "[%d] monitor failed: %d", descP->sock, res) ); + // esock_dbg_printf("MONP", "[%d] failed: %d\r\n", descP->sock, res); + } /* else { + esock_dbg_printf("MONP", + "[%d] success: " + "%u,%u,%u,%u\r\n", + descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + } */ + + return res; +} + + +static +int esock_demonitor(const char* slogan, + ErlNifEnv* env, + SocketDescriptor* descP, + ESockMonitor* monP) +{ + int res; + + SSDBG( descP, ("SOCKET", "[%d] %s: try demonitor\r\n", descP->sock, slogan) ); + /* + esock_dbg_printf("DEMONP", "[%d] %s: %u,%u,%u,%u\r\n", + descP->sock, slogan, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + */ + + res = enif_demonitor_process(env, descP, &monP->mon); + + if (res == 0) { + esock_monitor_init(monP); + } else { + SSDBG( descP, + ("SOCKET", "[%d] demonitor failed: %d\r\n", descP->sock, res) ); + /* + esock_dbg_printf("DEMONP", "[%d] failed: %d\r\n", descP->sock, res); + */ + } + + return res; +} + + +static +void esock_monitor_init(ESockMonitor* monP) +{ + int i; + + /* + * UGLY, + * but since we don't have a ERL_NIF_MONITOR_NULL, + * this will have to do for now... + */ + for (i = 0; i < 4; i++) + monP->raw[i] = 0; + +} + +/* +static +int esock_monitor_compare(const ErlNifMonitor* mon1, + const ESockMonitor* mon2) +{ + return enif_compare_monitors(mon1, &mon2->mon); +} +*/ + + +/* ---------------------------------------------------------------------- * C a l l b a c k F u n c t i o n s * ---------------------------------------------------------------------- */ @@ -16393,35 +16648,49 @@ static void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) { SocketDescriptor* descP = (SocketDescriptor*) obj; - + + /* + esock_dbg_printf("STOP", "[%d] begin\r\n", descP->sock); + */ + SSDBG( descP, - ("SOCKET", "socket_stop -> entry when" - "\r\n sock: %d (%d)" - "\r\n Is Direct Call: %s" - "\r\n", descP->sock, fd, B2S(is_direct_call)) ); + ("SOCKET", "socket_stop -> entry when %s" + "\r\n sock: %d (%d)" + "\r\n", ((is_direct_call) ? "called" : "scheduled"), descP->sock, fd) ); MLOCK(descP->writeMtx); MLOCK(descP->readMtx); MLOCK(descP->accMtx); MLOCK(descP->closeMtx); + SSDBG( descP, ("SOCKET", "socket_stop -> all mutex(s) locked\r\n") ); descP->state = SOCKET_STATE_CLOSING; // Just in case...??? descP->isReadable = FALSE; descP->isWritable = FALSE; - - + /* We should check that we actually have a monitor. * This *should* be done with a "NULL" monitor value, * which there currently is none... + * If we got here because the controlling process died, + * its no point in demonitor. Also, we not actually have + * a monitor in that case... */ - DEMONP(env, descP, &descP->ctrlMon); - + DEMONP("socket_stop -> ctrl", env, descP, &descP->ctrlMon); + + /* + esock_dbg_printf("STOP", "[%d] maybe handle current writer (0x%lX)\r\n", + descP->sock, descP->currentReaderP); + */ if (descP->currentWriterP != NULL) { /* We have a (current) writer and *may* therefor also have * writers waiting. */ + DEMONP("socket_stop -> current writer", + env, descP, &descP->currentWriter.mon); + + SSDBG( descP, ("SOCKET", "socket_stop -> handle current writer\r\n") ); if (!compare_pids(env, &descP->closerPid, &descP->currentWriter.pid) && @@ -16439,15 +16708,24 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) } /* And also deal with the waiting writers (in the same way) */ + SSDBG( descP, ("SOCKET", "socket_stop -> handle waiting writer(s)\r\n") ); inform_waiting_procs(env, descP, &descP->writersQ, TRUE, atom_closed); } - + + /* + esock_dbg_printf("STOP", "[%d] maybe handle current reader (0x%lX)\r\n", + descP->sock, descP->currentReaderP); + */ if (descP->currentReaderP != NULL) { /* We have a (current) reader and *may* therefor also have * readers waiting. */ + DEMONP("socket_stop -> current reader", + env, descP, &descP->currentReader.mon); + + SSDBG( descP, ("SOCKET", "socket_stop -> handle current reader\r\n") ); if (!compare_pids(env, &descP->closerPid, &descP->currentReader.pid) && @@ -16465,14 +16743,23 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) } /* And also deal with the waiting readers (in the same way) */ + SSDBG( descP, ("SOCKET", "socket_stop -> handle waiting reader(s)\r\n") ); inform_waiting_procs(env, descP, &descP->readersQ, TRUE, atom_closed); } - + + /* + esock_dbg_printf("STOP", "[%d] maybe handle current acceptor (0x%lX)\r\n", + descP->sock, descP->currentReaderP); + */ if (descP->currentAcceptorP != NULL) { /* We have a (current) acceptor and *may* therefor also have * acceptors waiting. */ + DEMONP("socket_stop -> current acceptor", + env, descP, &descP->currentAcceptor.mon); + + SSDBG( descP, ("SOCKET", "socket_stop -> handle current acceptor\r\n") ); if (!compare_pids(env, &descP->closerPid, &descP->currentAcceptor.pid) && @@ -16490,6 +16777,7 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) } /* And also deal with the waiting acceptors (in the same way) */ + SSDBG( descP, ("SOCKET", "socket_stop -> handle waiting acceptor(s)\r\n") ); inform_waiting_procs(env, descP, &descP->acceptorsQ, TRUE, atom_closed); } @@ -16521,7 +16809,8 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) send_msg(env, MKT2(env, atom_close, descP->closeRef), &descP->closerPid); - DEMONP(env, descP, &descP->closerMon); + DEMONP("socket_stop -> closer", + env, descP, &descP->closerMon); } else { @@ -16535,12 +16824,28 @@ void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) } } - + + if (!is_direct_call) { + if (descP->closeLocal) { + DEMONP("socket_stop -> closer", + env, descP, &descP->closerMon); + } + descP->sock = INVALID_SOCKET; + descP->event = INVALID_EVENT; + descP->state = SOCKET_STATE_CLOSED; + } + + SSDBG( descP, ("SOCKET", "socket_stop -> unlock all mutex(s)\r\n") ); + MUNLOCK(descP->closeMtx); MUNLOCK(descP->accMtx); MUNLOCK(descP->readMtx); MUNLOCK(descP->writeMtx); + /* + esock_dbg_printf("STOP", "[%d] end\r\n", descP->sock); + */ + SSDBG( descP, ("SOCKET", "socket_stop -> done (%d, %d)\r\n", descP->sock, fd) ); @@ -16580,7 +16885,8 @@ void inform_waiting_procs(ErlNifEnv* env, currentP->data.ref, reason, ¤tP->data.pid)) ); - DEMONP(env, descP, ¤tP->data.mon); + DEMONP("inform_waiting_procs -> current 'request'", + env, descP, ¤tP->data.mon); nextP = currentP->nextP; if (free) FREE(currentP); currentP = nextP; @@ -16605,30 +16911,159 @@ void socket_down(ErlNifEnv* env, const ErlNifMonitor* mon) { SocketDescriptor* descP = (SocketDescriptor*) obj; + ESockMonitor* monP = (ESockMonitor*) mon; SSDBG( descP, ("SOCKET", "socket_down -> entry with" - "\r\n sock: %d" - "\r\n pid: %T" - "\r\n", descP->sock, *pid) ); + "\r\n sock: %d" + "\r\n pid: %T" + "\r\n Close: %s (%s)" + "\r\n", + descP->sock, *pid, + B2S(IS_CLOSED(descP)), + B2S(IS_CLOSING(descP))) ); + /* + esock_dbg_printf("DOWN", + "[%d] begin %u,%u,%u,%d\r\n", + descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + */ + + /* + if (MON_COMP(mon, &descP->ctrlMon) == 0) { + SSDBG( descP, ("SOCKET", "socket_down -> controlling process mon\r\n") ); + } else if (MON_COMP(mon, &descP->closerMon) == 0) { + SSDBG( descP, ("SOCKET", "socket_down -> closer mon\r\n") ); + } else if ((descP->currentWriterP != NULL) && + (MON_COMP(mon, &descP->currentWriter.mon) == 0)) { + SSDBG( descP, ("SOCKET", "socket_down -> current writer mon\r\n") ); + } else if ((descP->currentReaderP != NULL) && + (MON_COMP(mon, &descP->currentReader.mon) == 0)) { + SSDBG( descP, ("SOCKET", "socket_down -> current reader mon\r\n") ); + } else if ((descP->currentAcceptorP != NULL) && + (MON_COMP(mon, &descP->currentAcceptor.mon) == 0)) { + SSDBG( descP, ("SOCKET", "socket_down -> current acceptor mon\r\n") ); + } else { + SSDBG( descP, ("SOCKET", "socket_down -> OTHER mon\r\n") ); + } + */ if (compare_pids(env, &descP->ctrlPid, pid)) { + int selectRes; + /* We don't bother with the queue cleanup here - * we leave it to the stop callback function. */ + SSDBG( descP, + ("SOCKET", "socket_down -> controlling process exit\r\n") ); + descP->state = SOCKET_STATE_CLOSING; descP->closeLocal = TRUE; descP->closerPid = *pid; - descP->closerMon = *mon; - descP->closeRef = MKREF(env); - enif_select(env, descP->sock, (ERL_NIF_SELECT_STOP), - descP, NULL, descP->closeRef); + descP->closerMon = (ESockMonitor) *mon; + descP->closeRef = MKREF(env); // Do we really need this in this case? + + /* + esock_dbg_printf("DOWN", + "[%d] select stop %u,%u,%u,%d\r\n", + descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + */ + + selectRes = enif_select(env, descP->sock, (ERL_NIF_SELECT_STOP), + descP, NULL, descP->closeRef); + + if (selectRes & ERL_NIF_SELECT_STOP_CALLED) { + /* We are done - wwe can finalize (socket close) directly */ + SSDBG( descP, + ("SOCKET", "socket_down -> [%d] stop called\r\n", descP->sock) ); + dec_socket(descP->domain, descP->type, descP->protocol); + descP->state = SOCKET_STATE_CLOSED; + + /* And finally close the socket. + * Since we close the socket because of an exiting owner, + * we do not need to wait for buffers to sync (linger). + * If the owner wish to ensure the buffer are written, + * it should have closed teh socket explicitly... + */ + if (sock_close(descP->sock) != 0) { + int save_errno = sock_errno(); + + esock_warning_msg("Failed closing socket for terminating " + "controlling process: " + "\r\n Controlling Process: %T" + "\r\n Descriptor: %d" + "\r\n Errno: %d" + "\r\n", pid, descP->sock, save_errno); + } + sock_close_event(descP->event); + + descP->sock = INVALID_SOCKET; + descP->event = INVALID_EVENT; + + descP->state = SOCKET_STATE_CLOSED; + + } else if (selectRes & ERL_NIF_SELECT_STOP_SCHEDULED) { + /* The stop callback function has been *scheduled* which means that + * "should" wait for it to complete. But since we are in a callback + * (down) function, we cannot... + * So, we must close the socket + */ + SSDBG( descP, + ("SOCKET", + "socket_down -> [%d] stop scheduled\r\n", descP->sock) ); + dec_socket(descP->domain, descP->type, descP->protocol); + + /* And now what? We can't wait for the stop function here... + * So, we simply close it here and leave the rest of the "close" + * for later (when the stop function actually gets called... + */ + + if (sock_close(descP->sock) != 0) { + int save_errno = sock_errno(); + + esock_warning_msg("Failed closing socket for terminating " + "controlling process: " + "\r\n Controlling Process: %T" + "\r\n Descriptor: %d" + "\r\n Errno: %d" + "\r\n", pid, descP->sock, save_errno); + } + sock_close_event(descP->event); + + } else { + + /* + * <KOLLA> + * + * WE SHOULD REALLY HAVE A WAY TO CLOBBER THE SOCKET, + * SO WE DON'T LET STUFF LEAK. + * NOW, BECAUSE WE FAILED TO SELECT, WE CANNOT FINISH + * THE CLOSE, WHAT TO DO? ABORT? + * + * </KOLLA> + */ + esock_warning_msg("Failed selecting stop when handling down " + "of controlling process: " + "\r\n Select Res: %d" + "\r\n Controlling Process: %T" + "\r\n Descriptor: %d" + "\r\n Monitor: %u.%u.%u.%u" + "\r\n", selectRes, pid, descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + } + } else { /* check all operation queue(s): acceptor, writer and reader. */ + SSDBG( descP, ("SOCKET", "socket_down -> other process term\r\n") ); + MLOCK(descP->accMtx); if (descP->currentAcceptorP != NULL) socket_down_acceptor(env, descP, pid); @@ -16645,6 +17080,14 @@ void socket_down(ErlNifEnv* env, MUNLOCK(descP->readMtx); } + + /* + esock_dbg_printf("DOWN", + "[%d] end %u,%u,%u,%d\r\n", + descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + */ SSDBG( descP, ("SOCKET", "socket_down -> done\r\n") ); diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index ff50fd2384..766d3724c1 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -1506,7 +1506,7 @@ void esock_warning_msg( const char* format, ... ) { va_list args; char f[512 + sizeof(format)]; // This has to suffice... - char stamp[32]; + char stamp[64]; // Just in case... struct timespec ts; int res; diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 1fff17cf8c..022e83a944 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -47,10 +47,12 @@ api_to_connect_tcp6/1, api_to_accept_tcp4/1, api_to_accept_tcp6/1, + api_to_maccept_tcp4/1, + api_to_maccept_tcp6/1, api_to_send_tcp4/1, api_to_send_tcp6/1, - api_to_sendapi_to_udp4/1, - api_to_sendapi_to_udp6/1, + api_to_sendto_udp4/1, + api_to_sendto_udp6/1, api_to_sendmsg_tcp4/1, api_to_sendmsg_tcp6/1, api_to_recv_udp4/1, @@ -62,7 +64,23 @@ api_to_recvmsg_udp4/1, api_to_recvmsg_udp6/1, api_to_recvmsg_tcp4/1, - api_to_recvmsg_tcp6/1 + api_to_recvmsg_tcp6/1, + + %% Socket Closure + sc_cpe_socket_cleanup_tcp4/1, + sc_cpe_socket_cleanup_tcp6/1, + sc_cpe_socket_cleanup_udp4/1, + sc_cpe_socket_cleanup_udp6/1, + sc_lc_recv_response_tcp4/1, + sc_lc_recv_response_tcp6/1, + sc_lc_recvmsg_response_tcp4/1, + sc_lc_recvmsg_response_tcp6/1, + sc_lc_acceptor_response_tcp4/1, + sc_lc_acceptor_response_tcp6/1, + sc_rc_recv_response_tcp4/1, + sc_rc_recv_response_tcp6/1, + sc_rc_recvmsg_response_tcp4/1, + sc_rc_recvmsg_response_tcp6/1 %% Tickets ]). @@ -71,6 +89,17 @@ %% -export([]). +-type initial_evaluator_state() :: map(). +-type evaluator_state() :: term(). +-type command_fun() :: + fun((State :: evaluator_state()) -> ok) | + fun((State :: evaluator_state()) -> {ok, evaluator_state()}) | + fun((State :: evaluator_state()) -> {error, term()}). + +-type command() :: #{desc := string(), + cmd := command_fun()}. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -define(BASIC_REQ, <<"hejsan">>). @@ -78,6 +107,8 @@ -define(FAIL(R), exit(R)). +-define(SLEEP(T), receive after T -> ok end). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -87,15 +118,20 @@ suite() -> all() -> [ - {group, api} + {group, api}, + {group, socket_closure} %% {group, tickets} ]. groups() -> [{api, [], api_cases()}, {api_basic, [], api_basic_cases()}, + {api_options, [], api_options_cases()}, {api_op_with_timeout, [], api_op_with_timeout_cases()}, - {api_options, [], api_options_cases()} + {socket_closure, [], socket_closure_cases()}, + {sc_ctrl_proc_exit, [], sc_cp_exit_cases()}, + {sc_local_close, [], sc_lc_cases()}, + {sc_remote_close, [], sc_rc_cases()} %% {tickets, [], ticket_cases()} ]. @@ -130,8 +166,8 @@ api_op_with_timeout_cases() -> api_to_accept_tcp6, api_to_send_tcp4, api_to_send_tcp6, - api_to_sendapi_to_udp4, - api_to_sendapi_to_udp6, + api_to_sendto_udp4, + api_to_sendto_udp6, api_to_sendmsg_tcp4, api_to_sendmsg_tcp6, api_to_recv_udp4, @@ -146,6 +182,48 @@ api_op_with_timeout_cases() -> api_to_recvmsg_tcp6 ]. +%% These cases tests what happens when the socket is closed, locally or +%% remotely. +socket_closure_cases() -> + [ + {group, sc_ctrl_proc_exit}, + {group, sc_local_close}, + {group, sc_remote_close} + ]. + +%% These cases are all about socket cleanup after the controlling process +%% exits *without* calling socket:close/1. +sc_cp_exit_cases() -> + [ + sc_cpe_socket_cleanup_tcp4, + sc_cpe_socket_cleanup_tcp6, + sc_cpe_socket_cleanup_udp4, + sc_cpe_socket_cleanup_udp6 + ]. + +%% These cases tests what happens when the socket is closed locally. +sc_lc_cases() -> + [ + sc_lc_recv_response_tcp4, + sc_lc_recv_response_tcp6, + + sc_lc_recvmsg_response_tcp4, + sc_lc_recvmsg_response_tcp6, + + sc_lc_acceptor_response_tcp4, + sc_lc_acceptor_response_tcp6 + ]. + +%% These cases tests what happens when the socket is closed remotely. +sc_rc_cases() -> + [ + sc_rc_recv_response_tcp4, + sc_rc_recv_response_tcp6, + + sc_rc_recvmsg_response_tcp4, + sc_rc_recvmsg_response_tcp6 + ]. + %% ticket_cases() -> %% []. @@ -169,6 +247,14 @@ end_per_testcase(_TC, Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API BASIC %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Basically open (create) and close an IPv4 UDP (dgram) socket. %% With some extra checks... @@ -177,9 +263,13 @@ api_b_open_and_close_udp4(suite) -> api_b_open_and_close_udp4(doc) -> []; api_b_open_and_close_udp4(_Config) when is_list(_Config) -> - tc_begin(api_b_open_and_close_udp4), - ok = api_b_open_and_close(inet, dgram, udp), - tc_end(). + tc_try(api_b_open_and_close_udp4, + fun() -> + InitState = #{domain => inet, + type => dgram, + protocol => udp}, + ok = api_b_open_and_close(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -191,35 +281,106 @@ api_b_open_and_close_tcp4(suite) -> api_b_open_and_close_tcp4(doc) -> []; api_b_open_and_close_tcp4(_Config) when is_list(_Config) -> - tc_begin(api_b_open_and_close_tcp4), - ok = api_b_open_and_close(inet, stream, tcp), - tc_end(). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -api_b_open_and_close(Domain, Type, Proto) -> - Socket = case socket:open(Domain, Type, Proto) of - {ok, S} -> - S; - {error, Reason} -> - ?FAIL({open, Reason}) - end, - %% Domain is not available on all platforms: - case socket:getopt(Socket, socket, domain) of - {ok, Domain} -> - ok; - {error, einval} -> - ok; - Else -> - ?FAIL({getopt, domain, Else}) - end, - {ok, Type} = socket:getopt(Socket, socket, type), - {ok, Proto} = socket:getopt(Socket, socket, protocol), - Self = self(), - {ok, Self} = socket:getopt(Socket, otp, controlling_process), - ok = socket:close(Socket), - ok. + tc_try(api_b_open_and_close_tcp4, + fun() -> + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = api_b_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_b_open_and_close(InitState) -> + Seq = + [ + #{desc => "open", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = S) -> + Res = socket:open(Domain, Type, Protocol), + {ok, {S, Res}} + end}, + #{desc => "validate open", + cmd => fun({S, {ok, Sock}}) -> + NewS = S#{socket => Sock}, + {ok, NewS}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get domain (maybe)", + cmd => fun(#{socket := Sock} = S) -> + Res = socket:getopt(Sock, socket, domain), + {ok, {S, Res}} + end}, + #{desc => "validate domain (maybe)", + cmd => fun({#{domain := Domain} = S, {ok, Domain}}) -> + {ok, S}; + ({#{domain := ExpDomain}, {ok, Domain}}) -> + {error, {unexpected_domain, ExpDomain, Domain}}; + %% Some platforms do not support this option + ({S, {error, einval}}) -> + {ok, S}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get type", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:getopt(Sock, socket, type), + {ok, {State, Res}} + end}, + #{desc => "validate type", + cmd => fun({#{type := Type} = State, {ok, Type}}) -> + {ok, State}; + ({#{type := ExpType}, {ok, Type}}) -> + {error, {unexpected_type, ExpType, Type}}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get protocol", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:getopt(Sock, socket, protocol), + {ok, {State, Res}} + end}, + #{desc => "validate protocol", + cmd => fun({#{protocol := Protocol} = State, {ok, Protocol}}) -> + {ok, State}; + ({#{protocol := ExpProtocol}, {ok, Protocol}}) -> + {error, {unexpected_type, ExpProtocol, Protocol}}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get controlling-process", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:getopt(Sock, otp, controlling_process), + {ok, {State, Res}} + end}, + #{desc => "validate controlling-process", + cmd => fun({State, {ok, Pid}}) -> + case self() of + Pid -> + {ok, State}; + _ -> + {error, {unexpected_owner, Pid}} + end; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "close socket", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:close(Sock), + {ok, {State, Res}} + end}, + #{desc => "validate socket close", + cmd => fun({_, ok}) -> + {ok, normal}; + ({_, {error, _} = ERROR}) -> + ERROR + end}], + Evaluator = evaluator_start("tester", Seq, InitState), + ok = await_evaluator_finish([Evaluator]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -231,15 +392,19 @@ api_b_sendto_and_recvfrom_udp4(suite) -> api_b_sendto_and_recvfrom_udp4(doc) -> []; api_b_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) -> - tc_begin(api_b_sendto_and_recvfrom_udp4), - Send = fun(Sock, Data, Dest) -> - socket:sendto(Sock, Data, Dest) - end, - Recv = fun(Sock) -> - socket:recvfrom(Sock) - end, - ok = api_b_send_and_recv_udp(inet, Send, Recv), - tc_end(). + tc_try(api_b_sendto_and_recvfrom_udp4, + fun() -> + Send = fun(Sock, Data, Dest) -> + socket:sendto(Sock, Data, Dest) + end, + Recv = fun(Sock) -> + socket:recvfrom(Sock) + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_udp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -251,45 +416,108 @@ api_b_sendmsg_and_recvmsg_udp4(suite) -> api_b_sendmsg_and_recvmsg_udp4(doc) -> []; api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> - tc_begin(api_b_sendmsg_and_recvmsg_udp4), - Send = fun(Sock, Data, Dest) -> - %% CMsgHdr = #{level => ip, type => tos, data => reliability}, - %% CMsgHdrs = [CMsgHdr], + tc_try(api_b_sendmsg_and_recvmsg_udp4, + fun() -> + Send = fun(Sock, Data, Dest) -> + %% CMsgHdr = #{level => ip, + %% type => tos, + %% data => reliability}, + %% CMsgHdrs = [CMsgHdr], MsgHdr = #{addr => Dest, %% ctrl => CMsgHdrs, iov => [Data]}, - socket:sendmsg(Sock, MsgHdr) - end, - Recv = fun(Sock) -> - case socket:recvmsg(Sock) of - {ok, #{addr := Source, - iov := [Data]}} -> - {ok, {Source, Data}}; - {error, _} = ERROR -> - ERROR - end - end, - ok = api_b_send_and_recv_udp(inet, Send, Recv), - tc_end(). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -api_b_send_and_recv_udp(Domain, Send, Recv) -> - SockSrc = sock_open(Domain, dgram, udp), - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - sock_bind(SockSrc, LSA), - SockDst = sock_open(Domain, dgram, udp), - sock_bind(SockDst, LSA), - Dst = sock_sockname(SockDst), - ok = Send(SockSrc, ?BASIC_REQ, Dst), - {ok, {Src, ?BASIC_REQ}} = Recv(SockDst), - ok = Send(SockDst, ?BASIC_REP, Src), - {ok, {Dst, ?BASIC_REP}} = Recv(SockSrc), - socket:close(SockSrc), - socket:close(SockDst), - ok. + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + iov := [Data]}} -> + {ok, {Source, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_b_send_and_recv_udp(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "open src socket", + cmd => fun(#{domain := Domain} = State) -> + Sock = sock_open(Domain, dgram, udp), + SASrc = sock_sockname(Sock), + {ok, State#{sock_src => Sock, sa_src => SASrc}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa := LSA}) -> + sock_bind(Sock, LSA), + ok + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + %% ei("src sockaddr: ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + #{desc => "open dst socket", + cmd => fun(#{domain := Domain} = State) -> + Sock = sock_open(Domain, dgram, udp), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa := LSA}) -> + sock_bind(Sock, LSA), + ok + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + %% ei("dst sockaddr: ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + #{desc => "send req (to dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + ok = Send(Sock, ?BASIC_REQ, Dst) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + {ok, {Src, ?BASIC_REQ}} = Recv(Sock), + ok + end}, + #{desc => "send rep (to src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) -> + ok = Send(Sock, ?BASIC_REP, Src) + end}, + #{desc => "recv rep (from dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) -> + {ok, {Dst, ?BASIC_REP}} = Recv(Sock), + ok + end}, + #{desc => "close src socket", + cmd => fun(#{sock_src := Sock}) -> + ok = socket:close(Sock) + end}, + #{desc => "close dst socket", + cmd => fun(#{sock_dst := Sock}) -> + ok = socket:close(Sock), + {ok, normal} + end} + ], + Evaluator = evaluator_start("tester", Seq, InitState), + ok = await_evaluator_finish([Evaluator]). @@ -302,15 +530,19 @@ api_b_send_and_recv_tcp4(suite) -> api_b_send_and_recv_tcp4(doc) -> []; api_b_send_and_recv_tcp4(_Config) when is_list(_Config) -> - tc_begin(api_b_send_and_recv_tcp4), - Send = fun(Sock, Data) -> - socket:send(Sock, Data) - end, - Recv = fun(Sock) -> - socket:recv(Sock) - end, - ok = api_b_send_and_recv_tcp(inet, Send, Recv), - tc_end(). + tc_try(api_b_send_and_recv_tcp4, + fun() -> + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -322,137 +554,184 @@ api_b_sendmsg_and_recvmsg_tcp4(suite) -> api_b_sendmsg_and_recvmsg_tcp4(doc) -> []; api_b_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> - tc_begin(api_b_sendmsg_and_recvmsg_tcp4), - Send = fun(Sock, Data) -> - MsgHdr = #{iov => [Data]}, - socket:sendmsg(Sock, MsgHdr) - end, - Recv = fun(Sock) -> - case socket:recvmsg(Sock) of - {ok, #{addr := undefined, - iov := [Data]}} -> - {ok, Data}; - {error, _} = ERROR -> - ERROR - end - end, - ok = api_b_send_and_recv_tcp(inet, Send, Recv), - tc_end(). + tc_try(api_b_sendmsg_and_recvmsg_tcp4, + fun() -> + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -api_b_send_and_recv_tcp(Domain, Send, Recv) -> +api_b_send_and_recv_tcp(InitState) -> process_flag(trap_exit, true), - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - Starter = self(), - ServerFun = fun() -> - put(sname, "server"), - %% Create the listen socket - ServerLSock = - case socket:open(Domain, stream, tcp) of - {ok, S1} -> - S1; - {error, ServerOR} -> - ?FAIL({server, open, ServerOR}) - end, - %% And bind it to the local address - SP = - case socket:bind(ServerLSock, LSA) of - {ok, P} -> - P; - {error, ServerBR} -> - ?FAIL({server, bind, ServerBR}) - end, - %% Listen for connecting clients - case socket:listen(ServerLSock) of - ok -> - ok; - {error, ServerLR} -> - ?FAIL({server, listen, ServerLR}) - end, - %% We are ready - Starter ! {self(), {ok, SP}}, - %% Accept connections - ServerSock = - case socket:accept(ServerLSock) of - {ok, Sock} -> - Sock; - {error, ServerAR} -> - ?FAIL({server, accept, ServerAR}) - end, - %% Wait for a message - case Recv(ServerSock) of - {ok, ?BASIC_REQ} -> - ok; - {error, ServerRR} -> - ?FAIL({server, recv, ServerRR}) - end, - %% Send the reply - case Send(ServerSock, ?BASIC_REP) of - ok -> - ok; - {error, ServerSR} -> - ?FAIL({server, send, ServerSR}) - end, - %% Close the sockets - socket:close(ServerSock), - socket:close(ServerLSock), - %% We are done - exit(normal) - end, - Server = spawn_link(ServerFun), - ServerPort = - receive - {Server, {ok, P}} -> - P; - {'EXIT', Server, ServerStartReason} -> - ?FAIL({server, start, ServerStartReason}) - end, - ClientSock = - case socket:open(Domain, stream, tcp) of - {ok, S2} -> - S2; - {error, ClientOR} -> - ?FAIL({client, open, ClientOR}) - end, - case socket:bind(ClientSock, LSA) of - {ok, _} -> - ok; - {error, ClientBR} -> - ?FAIL({client, bind, ClientBR}) - end, - case socket:connect(ClientSock, LSA#{port => ServerPort}) of - ok -> - ok; - {error, ClientCR} -> - ?FAIL({client, connect, ClientCR}) - end, - case Send(ClientSock, ?BASIC_REQ) of - ok -> - ok; - {error, ClientSR} -> - ?FAIL({client, send, ClientSR}) - end, - case Recv(ClientSock) of - {ok, ?BASIC_REP} -> - ok; - {ok, Msg} -> - ?FAIL({client, recv, {unexpected, Msg}}) - end, - receive - {'EXIT', Server, normal} -> - ok; - {'EXIT', Server, ServerStopReason} -> - ?FAIL({server, stop, ServerStopReason}) - end, - socket:close(ClientSock), - ok. + ServerSeq = + [ + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce server port", + cmd => fun(#{parent := Parent, lport := Port}) -> + ei("announcing port to parent (~p)", [Parent]), + Parent ! {server_port, self(), Port}, + ok + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ei("accepted: ~n ~p", [Sock]), + {ok, State#{tsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await request", + cmd => fun(#{tsock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, ?BASIC_REQ} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "send reply", + cmd => fun(#{tsock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "sleep some", + cmd => fun(_) -> + ?SLEEP(1000), + ok + end}, + #{desc => "close traffic socket", + cmd => fun(#{tsock := Sock}) -> + socket:close(Sock) + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock}) -> + socket:close(Sock) + end}, + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + ClientSeq = + [ + #{desc => "which server (local) address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + SSA = LSA#{port => Port}, + {ok, State#{lsa => LSA, ssa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = State) -> + case socket:bind(Sock, LSA) of + {ok, Port} -> + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, ssa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "send request (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "recv reply (from server)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + {ok, ?BASIC_REP} = Recv(Sock), + ok + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock}) -> + socket:close(Sock) + end}, + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start server evaluator"), + Server = evaluator_start("server", ServerSeq, InitState), + p("await server (~p) port", [Server]), + SPort = receive + {server_port, Server, Port} -> + Port + end, + p("start client evaluator"), + Client = evaluator_start("client", ClientSeq, InitState#{server_port => SPort}), + p("await evaluator(s)"), + ok = await_evaluator_finish([Server, Client]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API OPTIONS %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Perform some simple getopt and setopt with the level = otp options api_opt_simple_otp_options(suite) -> @@ -460,12 +739,10 @@ api_opt_simple_otp_options(suite) -> api_opt_simple_otp_options(doc) -> []; api_opt_simple_otp_options(_Config) when is_list(_Config) -> - tc_begin(api_opt_simple_otp_options), - - p("Create sockets"), - S1 = sock_open(inet, stream, tcp), - S2 = sock_open(inet, dgram, udp), + tc_try(api_opt_simple_otp_options, + fun() -> api_opt_simple_otp_options() end). +api_opt_simple_otp_options() -> Get = fun(S, Key) -> socket:getopt(S, otp, Key) end, @@ -473,70 +750,245 @@ api_opt_simple_otp_options(_Config) when is_list(_Config) -> socket:setopt(S, otp, Key, Val) end, - p("Create dummy process"), - Pid = spawn_link(fun() -> - put(sname, "dummy"), - receive - die -> - exit(normal) - end - end), - - F = fun(Sock) -> - p("Test IOW"), - {ok, IOW} = Get(Sock, iow), - NotIOW = not IOW, - ok = Set(Sock, iow, NotIOW), - {ok, NotIOW} = Get(Sock, iow), - - p("Test rcvbuf"), - {ok, RcvBuf} = Get(Sock, rcvbuf), - RcvBuf2 = RcvBuf*2, - ok = Set(Sock, rcvbuf, RcvBuf2), - {ok, RcvBuf2} = Get(Sock, rcvbuf), - ok = Set(Sock, rcvbuf, default), - {ok, RcvBuf} = Get(Sock, rcvbuf), - - p("Test rcvctrlbuf"), - {ok, RcvCtrlBuf} = Get(Sock, rcvctrlbuf), - RcvCtrlBuf2 = RcvCtrlBuf*2, - ok = Set(Sock, rcvctrlbuf, RcvCtrlBuf2), - {ok, RcvCtrlBuf2} = Get(Sock, rcvctrlbuf), - ok = Set(Sock, rcvctrlbuf, default), - {ok, RcvCtrlBuf} = Get(Sock, rcvctrlbuf), - - p("Test sndctrlbuf"), - {ok, SndCtrlBuf} = Get(Sock, sndctrlbuf), - SndCtrlBuf2 = SndCtrlBuf*2, - ok = Set(Sock, sndctrlbuf, SndCtrlBuf2), - {ok, SndCtrlBuf2} = Get(Sock, sndctrlbuf), - ok = Set(Sock, sndctrlbuf, default), - {ok, RcvCtrlBuf} = Get(Sock, sndctrlbuf), - - p("Test controlling-process"), - Self = self(), - {ok, Self} = Get(Sock, controlling_process), - ok = Set(Sock, controlling_process, Pid), - {ok, Pid} = Get(Sock, controlling_process) - - end, - - p("Test stream/tcp "), - F(S1), - - p("Test dgram/udp "), - F(S2), - - p("kill dummy process"), - %% This will also close its sockets (S1 and S2), - %% This should really be tested explicitly... - Pid ! die, - - %% p("close sockets"), - %% sock_close(S1), - %% sock_close(S2), - - tc_end(). + Seq = + [ + %% *** Init part *** + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + Sock = sock_open(Domain, Type, Protocol), + {ok, State#{sock => Sock}} + end}, + #{desc => "create dummy process", + cmd => fun(State) -> + Pid = spawn_link(fun() -> + put(sname, "dummy"), + receive + die -> + exit(normal) + end + end), + {ok, State#{dummy => Pid}} + end}, + + %% *** Check iow part *** + #{desc => "get iow", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, iow) of + {ok, IOW} when is_boolean(IOW) -> + {ok, State#{iow => IOW}}; + {ok, InvalidIOW} -> + {error, {invalid, InvalidIOW}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) iow", + cmd => fun(#{sock := Sock, iow := OldIOW} = State) -> + NewIOW = not OldIOW, + case Set(Sock, iow, NewIOW) of + ok -> + {ok, State#{iow => NewIOW}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) iow", + cmd => fun(#{sock := Sock, iow := IOW}) -> + case Get(Sock, iow) of + {ok, IOW} -> + ok; + {ok, InvalidIOW} -> + {error, {invalid, InvalidIOW}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Check rcvbuf part *** + #{desc => "get rcvbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, rcvbuf) of + {ok, RcvBuf} when is_integer(RcvBuf) -> + {ok, State#{rcvbuf => RcvBuf}}; + {ok, InvalidRcvBuf} -> + {error, {invalid, InvalidRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := OldRcvBuf} = State) -> + NewRcvBuf = 2 * OldRcvBuf, + case Set(Sock, rcvbuf, NewRcvBuf) of + ok -> + {ok, State#{rcvbuf => NewRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := RcvBuf}) -> + case Get(Sock, rcvbuf) of + {ok, RcvBuf} -> + ok; + {ok, InvalidRcvBuf} -> + {error, {invalid, InvalidRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Check rcvctrlbuf part *** + #{desc => "get rcvctrlbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) -> + {ok, State#{rcvctrlbuf => RcvCtrlBuf}}; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) -> + NewRcvCtrlBuf = 2 * OldRcvCtrlBuf, + case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of + ok -> + {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} -> + ok; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + %% *** Check rcvctrlbuf part *** + #{desc => "get rcvctrlbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) -> + {ok, State#{rcvctrlbuf => RcvCtrlBuf}}; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) -> + NewRcvCtrlBuf = 2 * OldRcvCtrlBuf, + case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of + ok -> + {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} -> + ok; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** Check sndctrlbuf part *** + #{desc => "get sndctrlbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, sndctrlbuf) of + {ok, SndCtrlBuf} when is_integer(SndCtrlBuf) -> + {ok, State#{sndctrlbuf => SndCtrlBuf}}; + {ok, InvalidSndCtrlBuf} -> + {error, {invalid, InvalidSndCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) sndctrlbuf", + cmd => fun(#{sock := Sock, sndctrlbuf := OldSndCtrlBuf} = State) -> + NewSndCtrlBuf = 2 * OldSndCtrlBuf, + case Set(Sock, sndctrlbuf, NewSndCtrlBuf) of + ok -> + {ok, State#{sndctrlbuf => NewSndCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) sndctrlbuf", + cmd => fun(#{sock := Sock, sndctrlbuf := SndCtrlBuf}) -> + case Get(Sock, sndctrlbuf) of + {ok, SndCtrlBuf} -> + ok; + {ok, InvalidSndCtrlBuf} -> + {error, {invalid, InvalidSndCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Check controlling-process part *** + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock}) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set dummy as controlling-process", + cmd => fun(#{sock := Sock, dummy := Dummy}) -> + Set(Sock, controlling_process, Dummy) + end}, + #{desc => "verify dummy as controlling-process", + cmd => fun(#{sock := Sock, dummy := Dummy}) -> + case Get(Sock, controlling_process) of + {ok, Dummy} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("Run test for stream/tcp socket"), + InitState1 = #{domain => inet, type => stream, protocol => tcp}, + Tester1 = evaluator_start("tcp-tester", Seq, InitState1), + p("await evaluator 1"), + ok = await_evaluator_finish([Tester1]), + + p("Run test for dgram/udp socket"), + InitState2 = #{domain => inet, type => dgram, protocol => udp}, + Tester2 = evaluator_start("udp-tester", Seq, InitState2), + p("await evaluator 2"), + ok = await_evaluator_finish([Tester2]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -547,12 +999,10 @@ api_opt_simple_otp_controlling_process(suite) -> api_opt_simple_otp_controlling_process(doc) -> []; api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) -> - tc_begin(api_opt_simple_otp_controlling_process), - - p("Create sockets"), - S1 = sock_open(inet, stream, tcp), - S2 = sock_open(inet, dgram, udp), + tc_try(api_opt_simple_otp_controlling_process, + fun() -> api_opt_simple_otp_controlling_process() end). +api_opt_simple_otp_controlling_process() -> Get = fun(S, Key) -> socket:getopt(S, otp, Key) end, @@ -560,113 +1010,245 @@ api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) -> socket:setopt(S, otp, Key, Val) end, - AwaitStart = - fun() -> - p("await start command"), - receive - {start, P, S} -> - {P, S} - end - end, - AwaitContinue = - fun(Pid) -> - p("await continue command"), - receive - {continue, Pid} -> - ok - end - end, - AwaitReady = - fun(Pid) -> - p("await ready confirmation from ~p", [Pid]), - receive - {ready, Pid} -> - ok - end - end, - AwaitDie = - fun(Pid) -> - p("await die command"), - receive - {die, Pid} -> - ok - end - end, - ClientStarter = - fun() -> - put(sname, "client"), - Self = self(), - {Parent, Sock} = AwaitStart(), - p("verify parent ~p controlling", [Parent]), - {ok, Parent} = Get(Sock, controlling_process), - p("attempt invalid control transfer (to self)"), - {error, not_owner} = Set(Sock, controlling_process, self()), - p("verify parent ~p (still) controlling", [Parent]), - {ok, Parent} = Get(Sock, controlling_process), - p("announce ready"), - Parent ! {ready, self()}, - - AwaitContinue(Parent), - p("verify self controlling"), - {ok, Self} = Get(Sock, controlling_process), - p("transfer control to parent ~p", [Parent]), - ok = Set(Sock, controlling_process, Parent), - p("attempt invalid control transfer (to self)"), - {error, not_owner} = Set(Sock, controlling_process, self()), - p("verify parent ~p controlling", [Parent]), - {ok, Parent} = Get(Sock, controlling_process), - p("announce ready"), - Parent ! {ready, self()}, - - AwaitDie(Parent), - p("done"), - exit(normal) - end, - - Tester = - fun(Sock, Client) -> - p("start"), - Self = self(), - p("verify self controlling"), - {ok, Self} = Get(Sock, controlling_process), - p("announce start"), - Client ! {start, Self, Sock}, - AwaitReady(Client), - - p("transfer control to client ~p", [Client]), - ok = Set(Sock, controlling_process, Client), - p("verify client ~p controlling", [Client]), - {ok, Client} = Get(Sock, controlling_process), - p("attempt invalid control transfer (to self)"), - {error, not_owner} = Set(Sock, controlling_process, self()), - p("announce continue"), - Client ! {continue, Self}, - AwaitReady(Client), - - p("verify self controlling"), - {ok, Self} = Get(Sock, controlling_process), - p("announce die"), - Client ! {die, Self}, - p("done"), - ok - end, + ClientSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + receive + {start, Tester, Socket} -> + {ok, State#{tester => Tester, + sock => Socket}} + end + end}, + #{desc => "verify tester as controlling-process", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + case Get(Sock, controlling_process) of + {ok, Tester} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt invalid controlling-process transfer (to self)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, controlling_process, self()) of + {error, not_owner} -> + ok; + ok -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (1)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester} = _State) -> + receive + {continue, Tester} -> + ok + end + end}, + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock} = _State) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt controlling-process transfer to tester", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + Set(Sock, controlling_process, Tester) + end}, + #{desc => "attempt invalid controlling-process transfer (to self)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, controlling_process, self()) of + {error, not_owner} -> + ok; + ok -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (2)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await termination", + cmd => fun(#{tester := Tester} = State) -> + receive + {terminate, Tester} -> + State1 = maps:remove(tester, State), + State2 = maps:remove(sock, State1), + {ok, State2} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + Sock = sock_open(Domain, Type, Protocol), + {ok, State#{sock => Sock}} + end}, + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock} = _State) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (client) start", + cmd => fun(#{client := Client, sock := Sock} = _State) -> + Client ! {start, self(), Sock}, + ok + end}, + #{desc => "await (client) ready (1)", + cmd => fun(#{client := Client} = _State) -> + receive + {ready, Client} -> + ok + end + end}, + #{desc => "attempt controlling-process transfer to client", + cmd => fun(#{client := Client, sock := Sock} = _State) -> + Set(Sock, controlling_process, Client) + end}, + #{desc => "verify client as controlling-process", + cmd => fun(#{client := Client, sock := Sock} = _State) -> + case Get(Sock, controlling_process) of + {ok, Client} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt invalid controlling-process transfer (to self)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, controlling_process, self()) of + {error, not_owner} -> + ok; + ok -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (client) continue", + cmd => fun(#{client := Client} = _State) -> + Client ! {continue, self()}, + ok + end}, + #{desc => "await (client) ready (2)", + cmd => fun(#{client := Client} = _State) -> + receive + {ready, Client} -> + ok + end + end}, + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock} = _State) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = State) -> + MRef = erlang:monitor(process, Client), + {ok, State#{client_mref => MRef}} + end}, + #{desc => "order (client) terminate", + cmd => fun(#{client := Client} = _State) -> + Client ! {terminate, self()}, + ok + end}, + #{desc => "await (client) down", + cmd => fun(#{client := Client} = State) -> + receive + {'DOWN', _, process, Client, _} -> + {ok, maps:remove(client, State)} + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("Run test for stream/tcp socket"), + ClientInitState1 = #{}, + Client1 = evaluator_start("tcp-client", ClientSeq, ClientInitState1), + TesterInitState1 = #{domain => inet, + type => stream, + protocol => tcp, + client => Client1}, + Tester1 = evaluator_start("tcp-tester", TesterSeq, TesterInitState1), + p("await stream/tcp evaluator"), + ok = await_evaluator_finish([Tester1, Client1]), + + p("Run test for dgram/udp socket"), + ClientInitState2 = #{}, + Client2 = evaluator_start("udp-client", ClientSeq, ClientInitState2), + TesterInitState2 = #{domain => inet, + type => dgram, + protocol => udp, + client => Client2}, + Tester2 = evaluator_start("udp-tester", TesterSeq, TesterInitState2), + p("await dgram/udp evaluator"), + ok = await_evaluator_finish([Tester2, Client2]). - p("Create Worker Process(s)"), - Pid1 = spawn_link(ClientStarter), - Pid2 = spawn_link(ClientStarter), - p("Test stream/tcp "), - Tester(S1, Pid1), - - p("Test dgram/udp "), - Tester(S2, Pid2), - - p("close sockets"), - sock_close(S1), - sock_close(S2), - - tc_end(). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API OPERATIONS WITH TIMEOUT %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -677,10 +1259,11 @@ api_to_connect_tcp4(suite) -> api_to_connect_tcp4(doc) -> []; api_to_connect_tcp4(_Config) when is_list(_Config) -> - tc_begin(api_to_connect_tcp4), - ok = api_to_connect_tcp(inet), - tc_end(). - %% not_yet_implemented(). + tc_try(api_to_connect_tcp4, + fun() -> + InitState = #{domain => inet, timeout => 5000}, + ok = api_to_connect_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -692,10 +1275,12 @@ api_to_connect_tcp6(suite) -> api_to_connect_tcp6(doc) -> []; api_to_connect_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_connect_tcp6), - %% ok = api_to_connect_tcp(inet6), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_connect_tcp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, timeout => 5000}, + ok = api_to_connect_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -705,77 +1290,256 @@ api_to_connect_tcp6(_Config) when is_list(_Config) -> %% For instance, on FreeBSD (11.2) the reponse when the backlog is full %% is a econreset. -api_to_connect_tcp(Domain) -> +api_to_connect_tcp(InitState) -> process_flag(trap_exit, true), - p("init"), - Client = self(), - LocalAddr = which_local_addr(Domain), - LocalSA = #{family => Domain, addr => LocalAddr}, - ServerName = f("~s:server", [get_tc_name()]), - Server = spawn_link(fun() -> - put(sname, ServerName), - p("open"), - LSock = sock_open(Domain, stream, tcp), - p("bind"), - ServerLPort = sock_bind(LSock, LocalSA), - p("listen on ~w", [ServerLPort]), - sock_listen(LSock, 1), - p("inform client"), - Client ! {self(), ServerLPort}, - p("await termination command"), - receive - die -> - p("terminating"), - exit(normal) - end - end), - - p("await server port"), - ServerLPort = - receive - {Server, Port} -> - Port - end, - p("open(s)"), - CSock1 = sock_open(Domain, stream, tcp), - CSock2 = sock_open(Domain, stream, tcp), - CSock3 = sock_open(Domain, stream, tcp), - p("bind(s)"), - _ClientPort1 = sock_bind(CSock1, LocalSA), - _ClientPort2 = sock_bind(CSock2, LocalSA), - _ClientPort3 = sock_bind(CSock3, LocalSA), - ServerSA = LocalSA#{port => ServerLPort}, - api_to_connect_tcp_await_timeout([CSock1, CSock2, CSock3], ServerSA), - p("terminate server"), - Server ! die, - receive - {'EXIT', Server, _} -> - p("server terminated"), - ok - end, - ok. - - -api_to_connect_tcp_await_timeout(Socks, ServerSA) -> - api_to_connect_tcp_await_timeout(Socks, ServerSA, 1). -api_to_connect_tcp_await_timeout([], _ServerSA, _ID) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket (with backlog = 1)", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock, 1) + end}, + #{desc => "monitor server", + cmd => fun(#{tester := Tester} = State) -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester_mref => MRef}} + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester, lport := Port}) -> + ei("announcing ready to tester (~p)", [Tester]), + Tester ! {ready, self(), Port}, + ok + end}, + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket 1", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock1 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "create socket 2", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock2 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "create socket 3", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock3 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket 1 to local address", + cmd => fun(#{sock1 := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket 2 to local address", + cmd => fun(#{sock2 := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket 3 to local address", + cmd => fun(#{sock3 := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Synchronize with the server *** + #{desc => "order (server) start", + cmd => fun(#{server := Server}) -> + Server ! {start, self()}, + ok + end}, + #{desc => "await ready (from server)", + cmd => fun(#{server := Server, lsa := LSA} = State) -> + receive + {ready, Server, Port} -> + {ok, State#{ssa => LSA#{port => Port}}} + end + end}, + + %% *** Connect sequence *** + #{desc => "order (server) start", + cmd => fun(#{sock1 := Sock1, + sock2 := Sock2, + sock3 := Sock3, + ssa := SSA, + timeout := To}) -> + Socks = [Sock1, Sock2, Sock3], + api_to_connect_tcp_await_timeout(Socks, To, SSA) + end}, + + %% *** Terminate server *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = State) -> + MRef = erlang:monitor(process, Server), + {ok, State#{server_mref => MRef}} + end}, + #{desc => "order (server) terminate", + cmd => fun(#{server := Server} = _State) -> + Server ! {terminate, self()}, + ok + end}, + #{desc => "await (server) down", + cmd => fun(#{server := Server} = State) -> + receive + {'DOWN', _, process, Server, _} -> + State1 = maps:remove(server, State), + State2 = maps:remove(ssa, State1), + {ok, State2} + end + end}, + + %% *** Close our sockets *** + #{desc => "close socket 3", + cmd => fun(#{sock3 := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock3, State)} + + end}, + #{desc => "close socket 2", + cmd => fun(#{sock2 := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock2, State)} + + end}, + #{desc => "close socket 1", + cmd => fun(#{sock1 := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock1, State)} + + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("create server evaluator"), + ServerInitState = InitState, + Server = evaluator_start("server", ServerSeq, ServerInitState), + + p("create tester evaluator"), + TesterInitState = InitState#{server => Server}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator(s)"), + ok = await_evaluator_finish([Server, Tester]). + + +api_to_connect_tcp_await_timeout(Socks, To, ServerSA) -> + api_to_connect_tcp_await_timeout(Socks, To, ServerSA, 1). + +api_to_connect_tcp_await_timeout([], _To, _ServerSA, _ID) -> ?FAIL(unexpected_success); -api_to_connect_tcp_await_timeout([Sock|Socks], ServerSA, ID) -> - p("~w: try connect", [ID]), - case socket:connect(Sock, ServerSA, 5000) of +api_to_connect_tcp_await_timeout([Sock|Socks], To, ServerSA, ID) -> + ei("~w: try connect", [ID]), + Start = t(), + case socket:connect(Sock, ServerSA, To) of {error, timeout} -> - p("expected timeout (~w)", [ID]), - ok; + ei("expected timeout (~w)", [ID]), + Stop = t(), + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end; {error, econnreset = Reason} -> - p("failed connecting: ~p - giving up", [Reason]), + ei("failed connecting: ~p - giving up", [Reason]), ok; {error, Reason} -> - p("failed connecting: ~p", [Reason]), - ?FAIL({recv, Reason}); + ee("failed connecting: ~p", [Reason]), + ?FAIL({connect, Reason}); ok -> - p("unexpected success (~w) - try next", [ID]), - api_to_connect_tcp_await_timeout(Socks, ServerSA, ID+1) + ei("unexpected success (~w) - try next", [ID]), + api_to_connect_tcp_await_timeout(Socks, To, ServerSA, ID+1) end. @@ -789,10 +1553,11 @@ api_to_accept_tcp4(suite) -> api_to_accept_tcp4(doc) -> []; api_to_accept_tcp4(_Config) when is_list(_Config) -> - %% tc_begin(api_to_accept_tcp4), - %% ok = api_to_accept_tcp(inet), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_accept_tcp4, + fun() -> + InitState = #{domain => inet, timeout => 5000}, + ok = api_to_accept_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -804,10 +1569,532 @@ api_to_accept_tcp6(suite) -> api_to_accept_tcp6(doc) -> []; api_to_accept_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_accept_tcp6), - %% ok = api_to_accept_tcp(inet6), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_accept_tcp4, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, timeout => 5000}, + ok = api_to_accept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_to_accept_tcp(InitState) -> + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + + %% *** The actual test part *** + #{desc => "attempt to accept (without success)", + cmd => fun(#{lsock := LSock, timeout := To} = State) -> + Start = t(), + case socket:accept(LSock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, Sock} -> + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + + %% *** Close (listen) socket *** + #{desc => "close (listen) socket", + cmd => fun(#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(sock3, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("create tester evaluator"), + Tester = evaluator_start("tester", TesterSeq, InitState), + + p("await evaluator"), + ok = await_evaluator_finish([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the multi accept timeout option +%% on an IPv4 TCP (stream) socket with multiple acceptor processes +%% (three in this case). +api_to_maccept_tcp4(suite) -> + []; +api_to_maccept_tcp4(doc) -> + []; +api_to_maccept_tcp4(_Config) when is_list(_Config) -> + tc_try(api_to_maccept_tcp4, + fun() -> + InitState = #{domain => inet, timeout => 5000}, + ok = api_to_maccept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the accept timeout option +%% on an IPv6 TCP (stream) socket. +api_to_maccept_tcp6(suite) -> + []; +api_to_maccept_tcp6(doc) -> + []; +api_to_maccept_tcp6(_Config) when is_list(_Config) -> + tc_try(api_to_maccept_tcp4, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, timeout => 5000}, + ok = api_to_maccept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_to_maccept_tcp(InitState) -> + PrimAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + receive + {start, Tester} -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester => Tester, + tester_mref => MRef}} + end + end}, + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + + #{desc => "announce ready", + cmd => fun(#{lsock := LSock, tester := Tester}) -> + ei("announcing port to tester (~p)", [Tester]), + Tester ! {ready, self(), LSock}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester} -> + ok + end + end}, + + %% *** The actual test part *** + #{desc => "attempt to accept (without success)", + cmd => fun(#{lsock := LSock, timeout := To} = State) -> + Start = t(), + case socket:accept(LSock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, Sock} -> + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester}) -> + ei("announcing port to tester (~p)", [Tester]), + Tester ! {ready, self()}, + ok + end}, + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + ok + end + end}, + + %% *** Close (listen) socket *** + #{desc => "close (listen) socket", + cmd => fun(#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + + SecAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + receive + {start, Tester, LSock} -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester => Tester, + lsock => LSock, + tester_mref => MRef}} + end + end}, + #{desc => "announce ready (1)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester} -> + ok + end + end}, + + %% *** The actual test part *** + #{desc => "attempt to accept (without success)", + cmd => fun(#{lsock := LSock, timeout := To} = State) -> + Start = t(), + case socket:accept(LSock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, Sock} -> + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready (2)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester, Reason}}; + {terminate, Tester} -> + ok + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + + TesterSeq = + [ + %% Init part + #{desc => "monitor prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + + %% Start the prim-acceptor + #{desc => "start prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await prim-acceptor ready (1)", + cmd => fun(#{prim_acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding prim-acceptor ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, prim_acceptor}}; + {ready, Pid, LSock} -> + {ok, State#{lsock => LSock}} + end + end}, + + %% Start sec-acceptor-1 + #{desc => "start sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid, lsock := LSock} = _State) -> + Pid ! {start, self(), LSock}, + ok + end}, + #{desc => "await sec-acceptor 1 ready (1)", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_1}}; + {ready, Pid} -> + ok + end + end}, + + %% Start sec-acceptor-2 + #{desc => "start sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid, lsock := LSock} = _State) -> + Pid ! {start, self(), LSock}, + ok + end}, + #{desc => "await sec-acceptor 2 ready (1)", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_2}}; + {ready, Pid} -> + ok + end + end}, + + %% Activate the acceptor(s) + #{desc => "active prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "active sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "active sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + + %% Await acceptor(s) completions + #{desc => "await prim-acceptor ready (2)", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding prim-acceptor ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, prim_acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await sec-acceptor 1 ready (2)", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_1}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await sec-acceptor 2 ready (2)", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_2}}; + {ready, Pid} -> + ok + end + end}, + + + %% Terminate the acceptor(s) + #{desc => "order prim-acceptor to terminate", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ei("send terminate command to prim-acceptor (~p)", [Pid]), + Pid ! {terminate, self()}, + ok + end}, + #{desc => "order sec-acceptor 1 to terminate", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ei("send terminate command to sec-acceptor-1 (~p)", [Pid]), + Pid ! {terminate, self()}, + ok + end}, + #{desc => "order sec-acceptor 2 to terminate", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ei("send terminate command to sec-acceptor-2 (~p)", [Pid]), + Pid ! {terminate, self()}, + ok + end}, + + %% Await acceptor(s) termination + #{desc => "await prim-acceptor termination", + cmd => fun(#{prim_acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + State1 = maps:remove(prim_acceptor, State), + {ok, State1} + end + end}, + #{desc => "await sec-acceptor 1 termination", + cmd => fun(#{sec_acceptor1 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + State1 = maps:remove(sec_acceptor1, State), + {ok, State1} + end + end}, + #{desc => "await sec-acceptor 2 termination", + cmd => fun(#{sec_acceptor2 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + State1 = maps:remove(sec_acceptor2, State), + {ok, State1} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("create prim-acceptor evaluator"), + PrimAInitState = InitState, + PrimAcceptor = evaluator_start("prim-acceptor", + PrimAcceptorSeq, PrimAInitState), + + p("create prim-acceptor 1 evaluator"), + SecAInitState1 = maps:remove(domain, InitState), + SecAcceptor1 = evaluator_start("sec-acceptor-1", + SecAcceptorSeq, SecAInitState1), + + p("create prim-acceptor 2 evaluator"), + SecAInitState2 = SecAInitState1, + SecAcceptor2 = evaluator_start("sec-acceptor-2", + SecAcceptorSeq, SecAInitState2), + + p("create tester evaluator"), + TesterInitState = #{prim_acceptor => PrimAcceptor, + sec_acceptor1 => SecAcceptor1, + sec_acceptor2 => SecAcceptor2}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator(s)"), + ok = await_evaluator_finish([PrimAcceptor, SecAcceptor1, SecAcceptor2, Tester]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -819,10 +2106,11 @@ api_to_send_tcp4(suite) -> api_to_send_tcp4(doc) -> []; api_to_send_tcp4(_Config) when is_list(_Config) -> - %% tc_begin(api_to_send_tcp4), - %% ok = api_to_send_tcp(inet), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_send_tcp4, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_send_tcp(inet) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -834,40 +2122,43 @@ api_to_send_tcp6(suite) -> api_to_send_tcp6(doc) -> []; api_to_send_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_send_tcp6), - %% ok = api_to_send_tcp(inet6), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_send_tcp6, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_send_tcp(inet6) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test the sendto timeout option %% on an IPv4 UDP (dgram) socket. -api_to_sendapi_to_udp4(suite) -> +api_to_sendto_udp4(suite) -> []; -api_to_sendapi_to_udp4(doc) -> +api_to_sendto_udp4(doc) -> []; -api_to_sendapi_to_udp4(_Config) when is_list(_Config) -> - %% tc_begin(api_to_sendapi_to_udp4), - %% ok = api_to_sendapi_to_udp(inet), - %% tc_end(). - not_yet_implemented(). +api_to_sendto_udp4(_Config) when is_list(_Config) -> + tc_try(api_to_sendto_udp4, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendto_to_udp(inet) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test the sendto timeout option %% on an IPv6 UDP (dgram) socket. -api_to_sendapi_to_udp6(suite) -> +api_to_sendto_udp6(suite) -> []; -api_to_sendapi_to_udp6(doc) -> +api_to_sendto_udp6(doc) -> []; -api_to_sendapi_to_udp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_sendapi_to_udp6), - %% ok = api_to_sendapi_to_udp(inet6), - %% tc_end(). - not_yet_implemented(). +api_to_sendto_udp6(_Config) when is_list(_Config) -> + tc_try(api_to_sendto_udp6, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendto_to_udp(inet6) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -879,10 +2170,11 @@ api_to_sendmsg_tcp4(suite) -> api_to_sendmsg_tcp4(doc) -> []; api_to_sendmsg_tcp4(_Config) when is_list(_Config) -> - %% tc_begin(api_to_sendmsg_tcp4), - %% ok = api_to_sendmsg_tcp(inet), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_sendmsg_tcp4, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendmsg_tcp(inet) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -894,10 +2186,11 @@ api_to_sendmsg_tcp6(suite) -> api_to_sendmsg_tcp6(doc) -> []; api_to_sendmsg_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_sendmsg_tcp6), - %% ok = api_to_sendmsg_tcp(inet6), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_sendmsg_tcp6, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendmsg_tcp(inet6) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -910,10 +2203,11 @@ api_to_recv_udp4(suite) -> api_to_recv_udp4(doc) -> []; api_to_recv_udp4(_Config) when is_list(_Config) -> - %% tc_begin(api_to_recv_udp4), - %% ok = api_to_recv_udp(inet), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_recv_udp4, + fun() -> + not_yet_implemented()%%, + %%ok = api_to_recv_udp(inet) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -926,10 +2220,11 @@ api_to_recv_udp6(suite) -> api_to_recv_udp6(doc) -> []; api_to_recv_udp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_recv_udp6), - %% ok = api_to_recv_udp(inet6), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_recv_udp6, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_recv_udp(inet6) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -941,9 +2236,14 @@ api_to_recv_tcp4(suite) -> api_to_recv_tcp4(doc) -> []; api_to_recv_tcp4(_Config) when is_list(_Config) -> - tc_begin(api_to_recv_tcp4), - ok = api_to_recv_tcp(inet), - tc_end(). + tc_try(api_to_recv_tcp4, + fun() -> + Recv = fun(Sock, To) -> socket:recv(Sock, 0, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -955,60 +2255,394 @@ api_to_recv_tcp6(suite) -> api_to_recv_tcp6(doc) -> []; api_to_recv_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_recv_tcp6), - %% ok = api_to_recv_tcp(inet6), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_recv_tcp6, + fun() -> + not_yet_implemented(), + case socket:supports(ipv6) of + true -> + Recv = fun(Sock, To) -> + socket:recv(Sock, 0, To) + end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_tcp(InitState); + false -> + skip("ipv6 not supported") + end + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -api_to_recv_tcp(Domain) -> +api_to_receive_tcp(InitState) -> process_flag(trap_exit, true), - p("server -> open"), - LSock = sock_open(Domain, stream, tcp), - LocalAddr = which_local_addr(Domain), - LocalSA = #{family => Domain, addr => LocalAddr}, - p("server -> bind"), - ServerLPort = sock_bind(LSock, LocalSA), - p("server(~w) -> listen", [ServerLPort]), - sock_listen(LSock), - ClientName = f("~s:client", [get_tc_name()]), - Client = spawn_link(fun() -> - put(sname, ClientName), - p("open"), - CSock = sock_open(Domain, stream, tcp), - p("bind"), - ClientPort = sock_bind(CSock, LocalSA), - p("[~w] connect to ~w", - [ClientPort, ServerLPort]), - sock_connect(CSock, LocalSA#{port => ServerLPort}), - p("await termination command"), - receive - die -> - p("terminating"), - exit(normal) - end - end), - p("server -> accept on ~w", [ServerLPort]), - Sock = sock_accept(LSock), - p("server -> recv"), - %% The zero (0) represents "give me everything you have" - case socket:recv(Sock, 0, 5000) of - {error, timeout} -> - p("server -> expected timeout"), - ok; - {ok, _Data} -> - ?FAIL(unexpected_success); - {error, Reason} -> - ?FAIL({recv, Reason}) - end, - Client ! die, - receive - {'EXIT', Client, _} -> - ok - end, - ok. + + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket (with backlog = 1)", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock, 1) + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = State) -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester_mref => MRef}} + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester, lport := Port}) -> + Tester ! {ready, self(), Port}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester}) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester} -> + ok + end + end}, + + %% *** The actual test *** + #{desc => "await accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + %% ok = socket:setopt(Sock, otp, debug, true), + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to recv (without success)", + cmd => fun(#{sock := Sock, recv := Recv, timeout := To} = State) -> + Start = t(), + case Recv(Sock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, _Data} -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready (recv timeout success)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + %% #{desc => "sleep some (before traffic close)", + %% cmd => fun(_) -> + %% ?SLEEP(1000), + %% ok + %% end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "close (traffic) socket", + cmd => fun(#{sock := Sock} = State) -> + %% ok = socket:setopt(Sock, otp, debug, true), + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + %% #{desc => "sleep some (before listen close)", + %% cmd => fun(_) -> + %% ?SLEEP(1000), + %% ok + %% end}, + #{desc => "close (listen) socket", + cmd => fun(#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(lsock, State)} + end}, + %% #{desc => "sleep some (after listen close)", + %% cmd => fun(_) -> + %% ?SLEEP(1000), + %% ok + %% end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester, Port} when is_pid(Tester) -> + {ok, State#{tester => Tester, + server_port => Port}} + end + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + SSA = LSA#{port => Port}, + {ok, State#{lsa => LSA, ssa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = State) -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester_mref => MRef}} + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (with connect)", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester} -> + ok + end + end}, + #{desc => "connect", + cmd => fun(#{sock := Sock, ssa := SSA}) -> + sock_connect(Sock, SSA), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = State) -> + MRef = erlang:monitor(process, Server), + {ok, State#{server_mref => MRef}} + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = State) -> + MRef = erlang:monitor(process, Client), + {ok, State#{client_mref => MRef}} + end}, + + %% *** Activate server *** + #{desc => "start server", + cmd => fun(#{server := Server} = _State) -> + Server ! {start, self()}, + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Server} = State) -> + receive + {'DOWN', _, process, Server, Reason} -> + {error, {unexpected_exit, server, Reason}}; + {ready, Server, Port} -> + {ok, State#{server_port => Port}} + end + end}, + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + Server ! {continue, self()}, + ok + end}, + + %% *** Activate client *** + #{desc => "start client", + cmd => fun(#{client := Client, server_port := Port} = _State) -> + Client ! {start, self(), Port}, + ok + end}, + #{desc => "await client ready", + cmd => fun(#{client := Client} = _State) -> + receive + {'DOWN', _, process, Client, Reason} -> + {error, {unexpected_exit, client, Reason}}; + {ready, Client} -> + ok + end + end}, + + %% *** The actual test *** + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + Client ! {continue, self()}, + ok + end}, + #{desc => "await server ready (accept/recv)", + cmd => fun(#{server := Server} = _State) -> + receive + {'DOWN', _, process, Server, Reason} -> + {error, {unexpected_exit, server, Reason}}; + {ready, Server} -> + ok + end + end}, + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + Client ! {terminate, self()}, + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + receive + {'DOWN', _, process, Client, _Reason} -> + State1 = maps:remove(client, State), + State2 = maps:remove(client_mref, State1), + {ok, State2} + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + Server ! {terminate, self()}, + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + receive + {'DOWN', _, process, Server, _Reason} -> + State1 = maps:remove(server, State), + State2 = maps:remove(server_mref, State1), + State3 = maps:remove(server_port, State2), + {ok, State3} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + + p("start server evaluator"), + ServerInitState = InitState, + Server = evaluator_start("server", ServerSeq, ServerInitState), + + p("start client evaluator"), + ClientInitState = InitState, + Client = evaluator_start("client", ClientSeq, ClientInitState), + + p("start tester evaluator"), + TesterInitState = #{server => Server, client => Client}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator(s)"), + ok = await_evaluator_finish([Server, Client, Tester]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1020,9 +2654,14 @@ api_to_recvfrom_udp4(suite) -> api_to_recvfrom_udp4(doc) -> []; api_to_recvfrom_udp4(_Config) when is_list(_Config) -> - tc_begin(api_to_recvfrom_udp4), - ok = api_to_recvfrom_udp(inet), - tc_end(). + tc_try(api_to_recvfrom_udp4, + fun() -> + Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_udp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1034,34 +2673,92 @@ api_to_recvfrom_udp6(suite) -> api_to_recvfrom_udp6(doc) -> []; api_to_recvfrom_udp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_recvfrom_udp6), - %% ok = api_to_recvfrom_udp(inet6), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_recvfrom_udp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_udp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -api_to_recvfrom_udp(Domain) -> - process_flag(trap_exit, true), - p("init"), - LocalAddr = which_local_addr(Domain), - LocalSA = #{family => Domain, addr => LocalAddr}, - p("open"), - Sock = sock_open(Domain, dgram, udp), - p("bind"), - _Port = sock_bind(Sock, LocalSA), - p("recv"), - case socket:recvfrom(Sock, 0, 5000) of - {error, timeout} -> - p("expected timeout"), - ok; - {ok, _SrcData} -> - ?FAIL(unexpected_success); - {error, Reason} -> - ?FAIL({recv, Reason}) - end, - ok. +api_to_receive_udp(InitState) -> + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** The actual test *** + #{desc => "attempt to read (without success)", + cmd => fun(#{sock := Sock, recv := Recv, timeout := To} = State) -> + Start = t(), + case Recv(Sock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, _} -> + {error, unexpected_sucsess}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + + %% *** Termination *** + #{desc => "close socket", + cmd => fun(#{sock := Sock} = _State) -> + sock_close(Sock), + ok + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start tester evaluator"), + Tester = evaluator_start("tester", TesterSeq, InitState), + + p("await evaluator"), + ok = await_evaluator_finish([Tester]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1073,10 +2770,14 @@ api_to_recvmsg_udp4(suite) -> api_to_recvmsg_udp4(doc) -> []; api_to_recvmsg_udp4(_Config) when is_list(_Config) -> - %% not_yet_implemented(). - tc_begin(api_to_recvmsg_udp4), - ok = api_to_recvmsg_udp(inet), - tc_end(). + tc_try(api_to_recvmsg_udp4, + fun() -> + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_udp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1088,34 +2789,15 @@ api_to_recvmsg_udp6(suite) -> api_to_recvmsg_udp6(doc) -> []; api_to_recvmsg_udp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_recvmsg_udp6), - %% ok = api_to_recvmsg_udp(inet6), - %% tc_end(). - not_yet_implemented(). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -api_to_recvmsg_udp(Domain) -> - process_flag(trap_exit, true), - p("init"), - LocalAddr = which_local_addr(Domain), - LocalSA = #{family => Domain, addr => LocalAddr}, - p("open"), - Sock = sock_open(Domain, dgram, udp), - p("bind"), - _Port = sock_bind(Sock, LocalSA), - p("recv"), - case socket:recvmsg(Sock, 5000) of - {error, timeout} -> - p("expected timeout"), - ok; - {ok, _MsgHdr} -> - ?FAIL(unexpected_success); - {error, Reason} -> - ?FAIL({recv, Reason}) - end, - ok. + tc_try(api_to_recvmsg_udp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_udp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1127,10 +2809,14 @@ api_to_recvmsg_tcp4(suite) -> api_to_recvmsg_tcp4(doc) -> []; api_to_recvmsg_tcp4(_Config) when is_list(_Config) -> - tc_begin(api_to_recvmsg_tcp4), - ok = api_to_recvmsg_tcp(inet), - tc_end(). - %% not_yet_implemented(). + tc_try(api_to_recvmsg_tcp4, + fun() -> + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_tcp(InitState) + end). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1142,62 +2828,1034 @@ api_to_recvmsg_tcp6(suite) -> api_to_recvmsg_tcp6(doc) -> []; api_to_recvmsg_tcp6(_Config) when is_list(_Config) -> - %% tc_begin(api_to_recvmsg_tcp6), - %% ok = api_to_recvmsg_tcp(inet6), - %% tc_end(). - not_yet_implemented(). + tc_try(api_to_recvmsg_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_tcp(InitState) + end). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% SOCKET CLOSURE %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -api_to_recvmsg_tcp(Domain) -> - process_flag(trap_exit, true), - p("server -> open"), - LSock = sock_open(Domain, stream, tcp), - LocalAddr = which_local_addr(Domain), - LocalSA = #{family => Domain, addr => LocalAddr}, - p("server -> bind"), - ServerLPort = sock_bind(LSock, LocalSA), - p("server(~w) -> listen", [ServerLPort]), - sock_listen(LSock), - ClientName = f("~s:client", [get_tc_name()]), - Client = spawn_link(fun() -> - put(sname, ClientName), - p("open"), - CSock = sock_open(Domain, stream, tcp), - p("bind"), - ClientPort = sock_bind(CSock, LocalSA), - p("[~w] connect to ~w", - [ClientPort, ServerLPort]), - sock_connect(CSock, LocalSA#{port => ServerLPort}), - p("await termination command"), - receive - die -> - p("terminating"), - exit(normal) - end - end), - p("server -> accept on ~w", [ServerLPort]), - Sock = sock_accept(LSock), - p("server -> recv"), - %% The zero (0) represents "give me everything you have" - case socket:recvmsg(Sock, 5000) of - {error, timeout} -> - p("server -> expected timeout"), - ok; - {ok, _Data} -> - ?FAIL(unexpected_success); - {error, Reason} -> - ?FAIL({recv, Reason}) - end, - Client ! die, - receive - {'EXIT', Client, _} -> - ok - end, +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% ("removed") when the controlling process terminates (without explicitly +%% calling the close function). For a IPv4 TCP (stream) socket. + +sc_cpe_socket_cleanup_tcp4(suite) -> + []; +sc_cpe_socket_cleanup_tcp4(doc) -> + []; +sc_cpe_socket_cleanup_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_tcp4, + fun() -> + %% not_yet_implemented(), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = sc_cpe_socket_cleanup(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% ("removed") when the controlling process terminates (without explicitly +%% calling the close function). For a IPv6 TCP (stream) socket. + +sc_cpe_socket_cleanup_tcp6(suite) -> + []; +sc_cpe_socket_cleanup_tcp6(doc) -> + []; +sc_cpe_socket_cleanup_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_tcp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, + type => stream, + protocol => tcp}, + ok = sc_cpe_socket_cleanup(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% ("removed") when the controlling process terminates (without explicitly +%% calling the close function). For a IPv4 UDP (dgram) socket. + +sc_cpe_socket_cleanup_udp4(suite) -> + []; +sc_cpe_socket_cleanup_udp4(doc) -> + []; +sc_cpe_socket_cleanup_udp4(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_udp4, + fun() -> + InitState = #{domain => inet, + type => dgram, + protocol => udp}, + ok = sc_cpe_socket_cleanup(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% (removed) when the controlling process terminates (without explicitly +%% calling the close function). For a IPv6 UDP (dgram) socket. + +sc_cpe_socket_cleanup_udp6(suite) -> + []; +sc_cpe_socket_cleanup_udp6(doc) -> + []; +sc_cpe_socket_cleanup_udp6(_Config) when is_list(_Config) -> + tc_try(sc_cpe_socket_cleanup_udp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, + type => dgram, + protocol => udp}, + ok = sc_cpe_socket_cleanup(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_cpe_socket_cleanup(InitState) -> + OwnerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + + %% *** Init part *** + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + Tester ! {ready, self(), Sock}, + ok + end}, + + %% *** The actual test *** + %% We intentially leave the socket "as is", no explicit close + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + %% #{desc => "enable (otp) debug", + %% cmd => fun(#{sock := Sock} = _State) -> + %% ok = socket:setopt(Sock, otp, debug, true) + %% end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor owner", + cmd => fun(#{owner := Owner} = _State) -> + _MRef = erlang:monitor(process, Owner), + ok + end}, + #{desc => "order (owner) start", + cmd => fun(#{owner := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await (owner) ready", + cmd => fun(#{owner := Owner} = State) -> + receive + {'DOWN', _, process, Owner, Reason} -> + ee("Unexpected DOWN regarding owner ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, owner}}; + {ready, Owner, Sock} -> + {ok, State#{sock => Sock}} + end + end}, + #{desc => "verify owner as controlling-process", + cmd => fun(#{owner := Owner, sock := Sock} = _State) -> + case socket:getopt(Sock, otp, controlling_process) of + {ok, Owner} -> + ok; + {ok, Other} -> + {error, {unexpected_owner, Other}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (owner) terminate", + cmd => fun(#{owner := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await (owner) termination", + cmd => fun(#{owner := Owner} = _State) -> + receive + {'DOWN', _, process, Owner, _} -> + ok + end + end}, + #{desc => "verify no socket (closed)", + cmd => fun(#{owner := Owner, sock := Sock} = _State) -> + case socket:getopt(Sock, otp, controlling_process) of + {ok, Pid} -> + {error, {unexpected_success, Owner, Pid}}; + {error, closed} -> + ok; + {error, Reason} -> + ei("expected failure: ~p", [Reason]), + {error, {unexpected_failure, Reason}} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start (socket) owner evaluator"), + Owner = evaluator_start("owner", OwnerSeq, InitState), + + p("start tester evaluator"), + TesterInitState = #{owner => Owner}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator"), + ok = await_evaluator_finish([Owner, Tester]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while a process is calling the recv function. +%% Socket is IPv4. +%% +%% <KOLLA> +%% +%% We should really have a similar test cases for when the controlling +%% process exits and there are other processes in recv, accept, and +%% all the other functions. +%% +%% </KOLLA> + +sc_lc_recv_response_tcp4(suite) -> + []; +sc_lc_recv_response_tcp4(doc) -> + []; +sc_lc_recv_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_recv_response_tcp4, + fun() -> + %% not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_lc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recv function. +%% Socket is IPv6. + +sc_lc_recv_response_tcp6(suite) -> + []; +sc_lc_recv_response_tcp6(doc) -> + []; +sc_lc_recv_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_lc_recv_response_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_lc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_lc_receive_response_tcp(InitState) -> + %% This is the server that accepts connections. + %% But it is also suppose to close the connection socket, + %% and trigger the read failure for the handler process. + AcceptorSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + Tester ! {ready, self(), Port}, + ok + end}, + + %% The actual test + #{desc => "await continue (connection)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester, Handler} -> + {ok, State#{handler => Handler}} + end + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ei("connection accepted"), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "transfer new connection to handler", + cmd => fun(#{handler := Handler, csock := Sock}) -> + ok = socket:setopt(Sock, + otp, controlling_process, + Handler), + Handler ! {connection, Sock}, + ok + end}, + #{desc => "announce ready (connection)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester} -> + ok + end + end}, + %% #{desc => "enable debug", + %% cmd => fun(#{csock := Sock}) -> + %% socket:setopt(Sock, otp, debug, true) + %% end}, + #{desc => "close (the connection) socket", + cmd => fun(#{csock := Sock}) -> + socket:close(Sock) + end}, + + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "socket cleanup", + cmd => fun(#{lsock := Sock} = State) -> + ok = socket:close(Sock), + State1 = maps:remove(csock, State), + State2 = maps:remove(lsock, State1), + State3 = maps:remove(lport, State2), + {ok, State3} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + %% The point of this is to perform the recv for which we are testing the reponse + HandlerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + #{desc => "monitor server", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + + %% The actual test + #{desc => "await connection socket", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {connection, Sock} -> + {ok, State#{sock => Sock}} + end + end}, + #{desc => "announce ready (connection)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + %% #{desc => "enable debug", + %% cmd => fun(#{sock := Sock}) -> + %% socket:setopt(Sock, otp, debug, true) + %% end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "attempt recv", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {ok, _Data} -> + ee("Unexpected data received"), + {error, unexpected_data}; + {error, closed} -> + State1 = maps:remove(sock, State), + {ok, State1}; + {error, Reason} = ERROR -> + ee("Unexpected read faulure: " + "~n ~p", [Reason]), + ERROR + end + end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "sleep some", + cmd => fun(_) -> + ?SLEEP(1000), + ok + end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + ok + end + end}, + + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + %% The point of this is basically just to create the connection. + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + + %% Init + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% The actual test + #{desc => "await continue", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester, Port} -> + {ok, State#{lport => Port}} + end + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, lsa := LSA, lport := LPort}) -> + socket:connect(Sock, LSA#{port => LPort}) + end}, + #{desc => "announce ready (connection)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% Cleaning up + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor acceptor", + cmd => fun(#{acceptor := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor handler", + cmd => fun(#{handler := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the acceptor + #{desc => "order acceptor start", + cmd => fun(#{acceptor := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await acceptor ready (init)", + cmd => fun(#{acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding acceptor ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid, Port} -> + {ok, State#{lport => Port}} + end + end}, + + %% Start the handler + #{desc => "order handler start", + cmd => fun(#{handler := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await handler ready (init)", + cmd => fun(#{handler := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding cient ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + + %% The actual test + #{desc => "order acceptor to continue", + cmd => fun(#{acceptor := Pid, handler := Handler} = _State) -> + Pid ! {continue, self(), Handler}, + ok + end}, + #{desc => "order client to continue", + cmd => fun(#{client := Pid, lport := Port} = _State) -> + Pid ! {continue, self(), Port}, + ok + end}, + #{desc => "await acceptor ready (connection)", + cmd => fun(#{acceptor := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding acceptor ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await client ready (connection)", + cmd => fun(#{client := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await handler ready (connection)", + cmd => fun(#{handler := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "sleep some", + cmd => fun(_State) -> + ?SLEEP(1000), + ok + end}, + #{desc => "order acceptor to continue (close)", + cmd => fun(#{acceptor := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "await handler ready (close)", + cmd => fun(#{handler := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + + %% Terminations + #{desc => "order handler to terminate", + cmd => fun(#{handler := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await handler termination", + cmd => fun(#{handler := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(handler, State)} + end + end}, + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(client, State)} + end + end}, + #{desc => "order acceptor to terminate", + cmd => fun(#{acceptor := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await acceptor termination", + cmd => fun(#{acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(acceptor, State)} + end + end}, + + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start acceptor evaluator"), + AccInitState = InitState, + Acceptor = evaluator_start("acceptor", AcceptorSeq, AccInitState), + + p("start handler evaluator"), + HandlerInitState = #{recv => maps:get(recv, InitState)}, + Handler = evaluator_start("handler", HandlerSeq, HandlerInitState), + + p("start client evaluator"), + ClientInitState = InitState, + Client = evaluator_start("client", ClientSeq, ClientInitState), + + p("start tester evaluator"), + TesterInitState = #{acceptor => Acceptor, + handler => Handler, + client => Client}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator"), + ok = await_evaluator_finish([Acceptor, Handler, Client, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% Socket is IPv4. + +sc_rc_recv_response_tcp4(suite) -> + []; +sc_rc_recv_response_tcp4(doc) -> + []; +sc_rc_recv_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rc_recv_response_tcp4, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% Socket is IPv6. + +sc_rc_recv_response_tcp6(suite) -> + []; +sc_rc_recv_response_tcp6(doc) -> + []; +sc_rc_recv_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rc_recv_response_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_rc_receive_response_tcp(_InitState) -> ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is IPv4. + +sc_lc_recvmsg_response_tcp4(suite) -> + []; +sc_lc_recvmsg_response_tcp4(doc) -> + []; +sc_lc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_recvmsg_response_tcp4, + fun() -> + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_lc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is IPv6. + +sc_lc_recvmsg_response_tcp6(suite) -> + []; +sc_lc_recvmsg_response_tcp6(doc) -> + []; +sc_lc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_recvmsg_response_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_lc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% Socket is IPv4. + +sc_rc_recvmsg_response_tcp4(suite) -> + []; +sc_rc_recvmsg_response_tcp4(doc) -> + []; +sc_rc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rc_recvmsg_response_tcp4, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% Socket is IPv6. + +sc_rc_recvmsg_response_tcp6(suite) -> + []; +sc_rc_recvmsg_response_tcp6(doc) -> + []; +sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rc_recvmsg_response_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. +%% Socket is IPv4. + +sc_lc_acceptor_response_tcp4(suite) -> + []; +sc_lc_acceptor_response_tcp4(doc) -> + []; +sc_lc_acceptor_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_acceptor_response_tcp4, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = sc_lc_acceptor_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. +%% Socket is IPv6. + +sc_lc_acceptor_response_tcp6(suite) -> + []; +sc_lc_acceptor_response_tcp6(doc) -> + []; +sc_lc_acceptor_response_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_lc_acceptor_response_tcp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = sc_lc_acceptor_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_lc_acceptor_response_tcp(_InitState) -> + ok. + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1232,6 +3890,104 @@ which_addr2(Domain, [_|IFO]) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% An evaluator is a process that executes a command sequence. +%% A test case will consist of atleast one evaluator (one for +%% each actor). +%% The evaluator process *always* run locally. Which means that +%% it will act as a "proxy" for remote nodes in necessary. +%% When the command sequence has been processed, the final state +%% will be used as exit reason. +%% A successful command shall evaluate to ok | {ok, NewState} + +-spec evaluator_start(Name, Seq, Init) -> {Pid, MRef} when + Name :: string(), + Seq :: [command()], + Init :: initial_evaluator_state(), + Pid :: pid(), + MRef :: reference(). + +evaluator_start(Name, Seq, Init) + when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) -> + Init2 = Init#{parent => self()}, + {Pid, _} = erlang:spawn_monitor(fun() -> evaluator_init(Name, Seq, Init2) end), + Pid. + +evaluator_init(Name, Seq, Init) -> + put(sname, Name), + evaluator_loop(1, Seq, Init). + +evaluator_loop(_ID, [], FinalState) -> + exit(FinalState); +evaluator_loop(ID, [#{desc := Desc, + cmd := Cmd}|Cmds], State) when is_function(Cmd, 1) -> + ei("evaluate command ~2w: ~s", [ID, Desc]), + try Cmd(State) of + ok -> + evaluator_loop(ID + 1, Cmds, State); + {ok, NewState} -> + evaluator_loop(ID + 1, Cmds, NewState); + {error, Reason} -> + ee("command ~w failed: " + "~n Reason: ~p", [ID, Reason]), + exit({command_failed, ID, Reason, State}) + catch + C:E:S -> + ee("command ~w crashed: " + "~n Class: ~p" + "~n Error: ~p" + "~n Call Stack: ~p", [ID, C, E, S]), + exit({command_crashed, ID, {C,E,S}, State}) + end. + +await_evaluator_finish(Evs) -> + await_evaluator_finish(Evs, []). + +await_evaluator_finish([], []) -> + ok; +await_evaluator_finish([], Fails) -> + Fails; +await_evaluator_finish(Evs, Fails) -> + receive + {'DOWN', _MRef, process, Pid, normal} -> + case lists:delete(Pid, Evs) of + Evs -> + p("unknown process ~p died (normal)", [Pid]), + await_evaluator_finish(Evs, Fails); + NewEvs -> + p("evaluator ~p success", [Pid]), + await_evaluator_finish(NewEvs, Fails) + end; + {'DOWN', _MRef, process, Pid, Reason} -> + case lists:delete(Pid, Evs) of + Evs -> + p("unknown process ~p died: " + "~n ~p", [Pid, Reason]), + await_evaluator_finish(Evs, Fails); + NewEvs -> + p("Evaluator ~p failed", [Pid]), + await_evaluator_finish(NewEvs, [{Pid, Reason}|Fails]) + end + end. + + +ei(F) -> + ei(F, []). +ei(F, A) -> + eprint("", F, A). + +ee(F) -> + ee(F, []). +ee(F, A) -> + eprint("<ERROR> ", F, A). + +eprint(Prefix, F, A) -> + io:format(user, "[~s][~s][~p] ~s" ++ F ++ "~n", + [formated_timestamp(), get(sname), self(), Prefix | A]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + sock_open(Domain, Type, Proto) -> try socket:open(Domain, Type, Proto) of {ok, Socket} -> @@ -1280,36 +4036,36 @@ sock_sockname(Sock) -> end. -sock_listen(Sock) -> - sock_listen2(fun() -> socket:listen(Sock) end). - -sock_listen(Sock, BackLog) -> - sock_listen2(fun() -> socket:listen(Sock, BackLog) end). - -sock_listen2(Listen) -> - try Listen() of - ok -> - ok; - {error, Reason} -> - ?FAIL({listen, Reason}) - catch - C:E:S -> - ?FAIL({listen, C, E, S}) - end. - - -sock_accept(LSock) -> - try socket:accept(LSock) of - {ok, Sock} -> - Sock; - {error, Reason} -> - p("sock_accept -> error: ~p", [Reason]), - ?FAIL({accept, Reason}) - catch - C:E:S -> - p("sock_accept -> failed: ~p, ~p, ~p", [C, E, S]), - ?FAIL({accept, C, E, S}) - end. +%% sock_listen(Sock) -> +%% sock_listen2(fun() -> socket:listen(Sock) end). + +%% sock_listen(Sock, BackLog) -> +%% sock_listen2(fun() -> socket:listen(Sock, BackLog) end). + +%% sock_listen2(Listen) -> +%% try Listen() of +%% ok -> +%% ok; +%% {error, Reason} -> +%% ?FAIL({listen, Reason}) +%% catch +%% C:E:S -> +%% ?FAIL({listen, C, E, S}) +%% end. + + +%% sock_accept(LSock) -> +%% try socket:accept(LSock) of +%% {ok, Sock} -> +%% Sock; +%% {error, Reason} -> +%% p("sock_accept -> error: ~p", [Reason]), +%% ?FAIL({accept, Reason}) +%% catch +%% C:E:S -> +%% p("sock_accept -> failed: ~p, ~p, ~p", [C, E, S]), +%% ?FAIL({accept, C, E, S}) +%% end. sock_close(Sock) -> @@ -1330,32 +4086,79 @@ sock_close(Sock) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% not_yet_implemented() -> - {skip, "not yet implemented"}. + skip("not yet implemented"). + +skip(Reason) -> + throw({skip, Reason}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +t() -> + os:timestamp(). + + +tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> + T1 = A1*1000000000+B1*1000+(C1 div 1000), + T2 = A2*1000000000+B2*1000+(C2 div 1000), + T2 - T1. + + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, _N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + %% {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + %% FormatTS = + %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w", + %% [YYYY, MM, DD, Hour, Min, Sec, N3]), + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]), + lists:flatten(FormatTS). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + set_tc_name(N) when is_atom(N) -> set_tc_name(atom_to_list(N)); set_tc_name(N) when is_list(N) -> put(tc_name, N). -get_tc_name() -> - get(tc_name). +%% get_tc_name() -> +%% get(tc_name). tc_begin(TC) -> set_tc_name(TC), p("begin ***"). -tc_end() -> - p("done ***"), +tc_end(Result) when is_list(Result) -> + p("done: ~s", [Result]), ok. +tc_try(Case, Fun) when is_atom(Case) andalso is_function(Fun, 0) -> + tc_begin(Case), + try + begin + Fun(), + tc_end("ok") + end + catch + throw:{skip, _} = SKIP -> + tc_end("skipping"), + SKIP; + Class:Error:Stack -> + tc_end("failed"), + erlang:raise(Class, Error, Stack) + end. + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -f(F, A) -> - lists:flatten(io_lib:format(F, A)). +%% f(F, A) -> +%% lists:flatten(io_lib:format(F, A)). p(F) -> p(F, []). @@ -1373,7 +4176,7 @@ p(F, A) -> Name when is_list(Name) -> Name end, - i("*** ~s[~p] " ++ F, [TcName,self()|A]). + i("*** [~s][~s][~p] " ++ F, [formated_timestamp(),TcName,self()|A]). %% i(F) -> |