diff --git a/include/ma_common.h b/include/ma_common.h
index 2d535e0f..762c9689 100644
--- a/include/ma_common.h
+++ b/include/ma_common.h
@@ -91,6 +91,8 @@ struct st_mysql_options_extension {
   int (*tls_verification_callback)(MARIADB_TLS *ctls, unsigned int flags);
   unsigned char zstd_compression_level;
   unsigned int max_columns;
+  void (*socket_callback)(void *data, my_socket handle, enum enum_mariadb_socket_event event);
+  void *socket_data;
 };
 
 typedef struct st_connection_handler
diff --git a/include/mariadb_com.h b/include/mariadb_com.h
index 90fbed80..501495a8 100644
--- a/include/mariadb_com.h
+++ b/include/mariadb_com.h
@@ -328,6 +328,13 @@ enum enum_mariadb_status_info
   SESSION_TRACK_TYPE
 };
 
+/* for socket event callback function */
+enum enum_mariadb_socket_event
+{
+  MARIADB_SOCKET_OPENED= 0,
+  MARIADB_SOCKET_CLOSING
+};
+
 enum enum_session_state_type
 {
   SESSION_TRACK_SYSTEM_VARIABLES= 0,
diff --git a/include/mysql.h b/include/mysql.h
index d88f6cf8..076ff370 100644
--- a/include/mysql.h
+++ b/include/mysql.h
@@ -263,6 +263,7 @@ extern const char *SQLSTATE_UNKNOWN;
     MARIADB_OPT_BULK_UNIT_RESULTS,
     MARIADB_OPT_TLS_VERIFICATION_CALLBACK,
     MARIADB_OPT_MAX_COLUMNS,
+    MARIADB_OPT_SOCKET_CALLBACK,
   };
 
   enum mariadb_value {
diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c
index 24d5d2d2..6bcf6776 100644
--- a/libmariadb/mariadb_lib.c
+++ b/libmariadb/mariadb_lib.c
@@ -4066,6 +4066,14 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...)
   case MYSQL_OPT_ZSTD_COMPRESSION_LEVEL:
     mysql->options.extension->zstd_compression_level = *((unsigned char *)arg1);
     break;
+  case MARIADB_OPT_SOCKET_CALLBACK:
+    {
+      void *arg2= va_arg(ap, void *);
+      mysql->options.extension->socket_callback=
+        (void (*)(void *, my_socket, enum enum_mariadb_socket_event))arg1;
+      mysql->options.extension->socket_data= arg2;
+    }
+    break;
   default:
     va_end(ap);
     SET_CLIENT_ERROR(mysql, CR_NOT_IMPLEMENTED, SQLSTATE_UNKNOWN, 0);
diff --git a/plugins/pvio/pvio_socket.c b/plugins/pvio/pvio_socket.c
index 5e873495..c566a938 100644
--- a/plugins/pvio/pvio_socket.c
+++ b/plugins/pvio/pvio_socket.c
@@ -174,6 +174,49 @@ error:
   return INVALID_SOCKET;
 }
 
+/* {{{ pvio_socket_notify */
+/* Report a socket lifetime event to the application. */
+static void pvio_socket_notify(MARIADB_PVIO *pvio, my_socket handle,
+                               enum enum_mariadb_socket_event event)
+{
+  MYSQL *mysql= pvio->mysql;
+
+  if (handle == INVALID_SOCKET)
+    return;
+  if (!mysql || !mysql->options.extension || !mysql->options.extension->socket_callback)
+    return;
+
+  mysql->options.extension->socket_callback(mysql->options.extension->socket_data,
+                                            handle, event);
+}
+/* }}} */
+
+/* {{{ pvio_socket_open_handle */
+/* Create a socket, announcing it before it is used for anything. */
+static my_socket pvio_socket_open_handle(MARIADB_PVIO *pvio, int domain, int type, int protocol)
+{
+  my_socket handle= new_nonblocking_socket(domain, type, protocol);
+
+  pvio_socket_notify(pvio, handle, MARIADB_SOCKET_OPENED);
+
+  return handle;
+}
+/* }}} */
+
+/* {{{ pvio_socket_close_handle */
+/* Close the socket, announcing it first. */
+static int pvio_socket_close_handle(MARIADB_PVIO *pvio, struct st_pvio_socket *csock)
+{
+  int r;
+
+  pvio_socket_notify(pvio, csock->socket, MARIADB_SOCKET_CLOSING);
+  r= closesocket(csock->socket);
+  csock->socket= INVALID_SOCKET;
+
+  return r;
+}
+/* }}} */
+
 static my_bool pvio_socket_initialized= FALSE;
 
 static int pvio_socket_init(char *errmsg __attribute__((unused)),
@@ -669,7 +712,7 @@ my_bool pvio_socket_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo)
 #ifdef HAVE_SYS_UN_H
     size_t port_length;
     struct sockaddr_un UNIXaddr;
-    if ((csock->socket = new_nonblocking_socket(AF_UNIX,SOCK_STREAM,0)) == INVALID_SOCKET ||
+    if ((csock->socket = pvio_socket_open_handle(pvio,AF_UNIX,SOCK_STREAM,0)) == INVALID_SOCKET ||
         (port_length=strlen(cinfo->unix_socket)) >= (sizeof(UNIXaddr.sun_path)))
     {
       PVIO_SET_ERROR(cinfo->mysql, CR_SOCKET_CREATE_ERROR, unknown_sqlstate, 0, errno);
@@ -785,9 +828,9 @@ my_bool pvio_socket_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo)
     {
       /* CONC-364: Avoid leak of open sockets */
       if (csock->socket != INVALID_SOCKET)
-        closesocket(csock->socket);
-      csock->socket= new_nonblocking_socket(save_res->ai_family, save_res->ai_socktype,
-                                        save_res->ai_protocol);
+        pvio_socket_close_handle(pvio, csock);
+      csock->socket= pvio_socket_open_handle(pvio, save_res->ai_family,
+                                             save_res->ai_socktype, save_res->ai_protocol);
       if (csock->socket == INVALID_SOCKET)
         /* Errors will be handled after loop finished */
         continue;
@@ -801,8 +844,7 @@ my_bool pvio_socket_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo)
         }
         if (rc)
         {
-          closesocket(csock->socket);
-          csock->socket= INVALID_SOCKET;
+          pvio_socket_close_handle(pvio, csock);
           continue;
         }
       }
@@ -854,8 +896,7 @@ error:
   /* close socket: MDEV-10891 */
   if (csock->socket != INVALID_SOCKET)
   {
-    closesocket(csock->socket);
-    csock->socket= INVALID_SOCKET;
+    pvio_socket_close_handle(pvio, csock);
   }
   if (pvio->data)
   {
@@ -879,7 +920,7 @@ my_bool pvio_socket_close(MARIADB_PVIO *pvio)
     csock= (struct st_pvio_socket *)pvio->data;
     if (csock && csock->socket != INVALID_SOCKET)
     {
-      r= closesocket(csock->socket);
+      r= pvio_socket_close_handle(pvio, csock);
       csock->socket= INVALID_SOCKET;
     }
     free((gptr)pvio->data);
diff --git a/unittest/libmariadb/connection.c b/unittest/libmariadb/connection.c
index 5d63086e..77011fa6 100644
--- a/unittest/libmariadb/connection.c
+++ b/unittest/libmariadb/connection.c
@@ -2778,6 +2778,103 @@ error:
   return ret;
 }
 
+struct socket_events
+{
+  my_socket opened;
+  my_socket closing;
+  int       opened_count;
+  int       closing_count;
+  int       closing_valid;
+};
+
+static void socket_event_handler(void *data, my_socket handle,
+                                 enum enum_mariadb_socket_event event)
+{
+  struct socket_events *events= (struct socket_events *)data;
+  int type;
+#ifdef _WIN32
+  int len= sizeof(type);
+#else
+  socklen_t len= sizeof(type);
+#endif
+
+  switch (event)
+  {
+  case MARIADB_SOCKET_OPENED:
+    events->opened= handle;
+    events->opened_count++;
+    break;
+  case MARIADB_SOCKET_CLOSING:
+    events->closing_valid= (getsockopt(handle, SOL_SOCKET, SO_TYPE,
+                                       (char *)&type, &len) == 0);
+    events->closing= handle;
+    events->closing_count++;
+    break;
+  }
+}
+
+static int test_socket_callback(MYSQL *my __attribute__((unused)))
+{
+  MYSQL *mysql= mysql_init(NULL);
+  struct socket_events events;
+  my_socket handle;
+
+  memset(&events, 0, sizeof(events));
+  mysql_optionsv(mysql, MARIADB_OPT_SOCKET_CALLBACK, (void *)socket_event_handler,
+                 (void *)&events);
+
+  if (!my_test_connect(mysql, hostname, username, password, schema, port, socketname, 0, 1))
+  {
+    diag("Error: %s", mysql_error(mysql));
+    mysql_close(mysql);
+    return FAIL;
+  }
+
+  handle= mysql_get_socket(mysql);
+
+  FAIL_IF(events.opened_count < 1, "No MARIADB_SOCKET_OPENED event");
+  FAIL_IF(events.opened != handle, "Opened handle differs from mysql_get_socket()");
+  /* Every socket but the one in use has been reported closing already */
+  FAIL_IF(events.closing_count != events.opened_count - 1, "Unbalanced socket events");
+
+  mysql_close(mysql);
+
+  FAIL_IF(events.closing_count != events.opened_count, "No MARIADB_SOCKET_CLOSING event");
+  FAIL_IF(events.closing != handle, "Wrong handle reported closing");
+  FAIL_IF(!events.closing_valid, "Handle was already closed when the callback ran");
+
+  return OK;
+}
+
+static int test_socket_callback_failed_connect(MYSQL *my __attribute__((unused)))
+{
+  MYSQL *mysql= mysql_init(NULL);
+  struct socket_events events;
+  unsigned int timeout= 5;
+
+  memset(&events, 0, sizeof(events));
+  mysql_optionsv(mysql, MARIADB_OPT_SOCKET_CALLBACK, (void *)socket_event_handler,
+                 (void *)&events);
+  mysql_optionsv(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&timeout);
+
+  /* Port 1 refuses, so the socket is created and closed without a server */
+  if (my_test_connect(mysql, hostname, username, password, schema, 1, NULL, 0, 1))
+  {
+    diag("Error expected: connect to port 1 succeeded");
+    mysql_close(mysql);
+    return FAIL;
+  }
+  diag("Error (expected): %s", mysql_error(mysql));
+
+  FAIL_IF(events.opened_count < 1, "No MARIADB_SOCKET_OPENED event");
+  FAIL_IF(events.closing_count != events.opened_count,
+          "Socket closed on the error path without a MARIADB_SOCKET_CLOSING event");
+  FAIL_IF(!events.closing_valid, "Handle was already closed when the callback ran");
+
+  mysql_close(mysql);
+  return OK;
+}
+
 struct my_tests_st my_tests[] = {
 #ifdef WIN32
   {"test_conc760", test_conc760, TEST_CONNECTION_DEFAULT, 0, NULL, NULL},
@@ -2838,6 +2935,8 @@ struct my_tests_st my_tests[] = {
   {"test_connection_timeout2", test_connection_timeout2, TEST_CONNECTION_NONE, 0, NULL, NULL},
   {"test_connection_timeout3", test_connection_timeout3, TEST_CONNECTION_NONE, 0, NULL, NULL},
   {"test_tls_timeout", test_tls_timeout, TEST_CONNECTION_NONE, 0, NULL, NULL},
+  {"test_socket_callback", test_socket_callback, TEST_CONNECTION_NONE, 0, NULL, NULL},
+  {"test_socket_callback_failed_connect", test_socket_callback_failed_connect, TEST_CONNECTION_NONE, 0, NULL, NULL},
   {NULL, NULL, 0, 0, NULL, NULL}
 };
 
