aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMicael Karlberg <[email protected]>2018-10-18 17:24:19 +0200
committerMicael Karlberg <[email protected]>2018-10-18 17:24:19 +0200
commita9de37dd6d2d15aa5d569e952f90c16b5e6a3ff5 (patch)
tree7702b7a519fc8bdf6e3ce17c7e2bdd19e8743e2f
parentff3d31f7d102d68a585f8ce636753bf48b9553ab (diff)
parent3c4c36587df5a847dd6f03011a4ecb76d0b70b40 (diff)
downloadotp-a9de37dd6d2d15aa5d569e952f90c16b5e6a3ff5.tar.gz
otp-a9de37dd6d2d15aa5d569e952f90c16b5e6a3ff5.tar.bz2
otp-a9de37dd6d2d15aa5d569e952f90c16b5e6a3ff5.zip
Merge branch 'bmk/20181004/nififying_inet_rework_test_suite/OTP-14831' into bmk/20180918/nififying_inet/OTP-14831
-rw-r--r--erts/emulator/nifs/common/socket_int.h8
-rw-r--r--erts/emulator/nifs/common/socket_nif.c587
-rw-r--r--erts/emulator/nifs/common/socket_util.c2
-rw-r--r--lib/kernel/test/socket_SUITE.erl4227
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,
&currentP->data.pid)) );
- DEMONP(env, descP, &currentP->data.mon);
+ DEMONP("inform_waiting_procs -> current 'request'",
+ env, descP, &currentP->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) ->