Skip to content

Improve remote shell loop error handling - #851

Open
pnoltes wants to merge 4 commits into
masterfrom
feature/improve-remote-shell-loop-handling
Open

pnoltes wants to merge 4 commits into
masterfrom
feature/improve-remote-shell-loop-handling

Conversation

@pnoltes

@pnoltes pnoltes commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

Improve remote shell loop by recv return and then logging result and exiting loop if needed.

@codecov-commenter

codecov-commenter commented Sep 27, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 53.84615% with 66 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.55%. Comparing base (270c784) to head (885c8e4).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
...e_service_admin_dfi/src/remote_service_admin_dfi.c 60.67% 35 Missing ⚠️
bundles/http_admin/http_admin/src/http_admin.c 42.59% 31 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##           master     #851    +/-   ##
========================================
  Coverage   91.54%   91.55%            
========================================
  Files         235      235            
  Lines       28787    28961   +174     
========================================
+ Hits        26353    26514   +161     
- Misses       2434     2447    +13     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@PengZheng PengZheng left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Though networking bundles are not robust enough to be used in production environment, they are still instructive to help programmers learn how to integrate Celix. It's OK that we add some basic protections first before a refactoring.

Comment thread bundles/shell/remote_shell/src/remote_shell.c Outdated

@PengZheng PengZheng left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice improvement for our networking code.
The HTTP code is good enough except for its zero return handling.
The TCP part is more difficult to deal with raw socket API.

Comment thread bundles/http_admin/http_admin/src/http_admin.c Outdated
Comment thread bundles/remote_services/remote_service_admin_dfi/src/remote_service_admin_dfi.c Outdated
} else { //error
remoteShell_connection_print(connection, RS_ERROR);
remoteShell_connection_print(connection, RS_PROMPT);
len = recv(fd, buff + used, COMMAND_BUFF_SIZE - 1 - used, 0);

@PengZheng PengZheng Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still not robust enough. A common error breaking this is EINTR, which requires retry.
Note EINTR is not the only such status code, EAGAIN/EWOULDBLOCK to name a few.
We also need to take platform difference (macOS and Linux) into account.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, also added a specific function to check EAGAIN/EWOULDBLOCK, so that we can make this platform dependent in a cohesive way.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This applies to sockets in non-blocking mode.
I have not checked whether sockets in remote_shell has been set in this mode.

@PengZheng PengZheng left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some further remarks, some of which need to be addressed before merging this PR.

}
totalBytesRead += (size_t)read;
}
if (totalBytesRead < length) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if contentLength == -1? Then we can never receive a request without explicit content_length successfully.

*errorMsgOut = NULL;
if (contentLength > 0) {
size_t length = (contentLength > INT_MAX) ? INT_MAX : (size_t)contentLength;
buffer = malloc(length + 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we apply some upper bound to request body size?
For a request without explicit content-length (Transfer-Encoding: chunked), it is not safe to allocate so much memory.
And it would be nice to have test cases for both such unusually large requests and reqeusts with chunked transfer-encoding.

Or we may explicitly refuse requests without content-length by returning 411 (Length Required).
This way we cannot process chunked requests.


#define CELIX_HTTP_ADMIN_READ_ERROR_MSG "Bad Request: Read error"
#define CELIX_HTTP_ADMIN_INCOMPLETE_BODY_MSG "Bad Request: Incomplete Body"
#define CELIX_HTTP_ADMIN_INSUFFICIENT_MEM_MSG "Server Error: Insufficient memory"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The status texts like "Bad Request"/"server Error" are redundent according to mg_send_http_error documentation:

The status text (e.g., "Not Found") for standard error codes is known by this function. A body of the error message, to explain the error in more detail, can be specified using the fmt format specifier and additional arguments. The fmt format specifier works like for the printf() function in the standard C library.

Check https://github.com/civetweb/civetweb/blob/588860e30721bf5453b0440c390865a8e85dcae5/examples/rest/rest.c#L94 for example usage.

}
if (contentLength > 0 && (unsigned long long)contentLength > maxRequestBodySize) {
RSA_LOG_ERROR(rsa, "Error while reading request body, the body is larger than the maximum of %zu bytes", maxRequestBodySize);
mg_send_http_error(conn, 413, "%s", "Request body too large");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As already mentioned, mg_send_http_error will add status text corresponding to error code, as in https://github.com/civetweb/civetweb/blob/588860e30721bf5453b0440c390865a8e85dcae5/src/civetweb.c#L4677

This remark applies to all usages of this API.

if (rc != CELIX_SUCCESS) {
RSA_LOG_ERROR(rsa, "Error trying to invoke remove service, got error %i\n", rc);
}
if (contentLength > 0 && (unsigned long long)contentLength > maxRequestBodySize) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that 0 is a valid Content-Length value, which just means message body is empty.
This is invalid for the remote service scenario, and thus we can avoid the following memory allocation and return error early.

RSA_LOG_ERROR(rsa, "Error trying to invoke remove service, got error %i\n", rc);
mg_send_http_error(conn, 500, "%s", "Internal server error");
} else if (response != NULL) {
mg_write(conn, data_response_headers, strlen(data_response_headers));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not make sense to send data_response_headers and response differently.
The return value of the former send is not checked at all.

break;
}
}
if (contentLength <= 0 && readLen > maxRequestBodySize) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

contentLength == 0 is invalid for our use case.

for (i = 0; i < length; i += 1) {
connection_pt connection = celix_arrayList_get(instance->connections, i);
connection->threadRunning = false;
connection_setThreadRunning(connection, false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have found no synchronization between connection processing thread and the calling thread, and thus suspect use-after-free bugs here.
Moreover, we never calling pthread_join on these connection processing thread, these are definitely resource leaks.

} while (result == -1 && errno == EINTR && connection_isThreadRunning(connection));

/* The socket_fd has data available to be read */
if (result > 0 && FD_ISSET(fd, &connection->pollset)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if result == -1 with a non-trivial errno? Should we quit?

} else { //error
remoteShell_connection_print(connection, RS_ERROR);
remoteShell_connection_print(connection, RS_PROMPT);
len = recv(fd, buff + used, COMMAND_BUFF_SIZE - 1 - used, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This applies to sockets in non-blocking mode.
I have not checked whether sockets in remote_shell has been set in this mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants