GSoC with GNU Wget2 - Part I
This series of writing summing up of what I did in Google Summer of Code (GSoC) 2017. GSoC 2017 be held in three period of time. In this part I will tell you about the first period.
Community Approaching
After looking for organizations to work on, I decide to choose GNU Wget2 (part of GNU Project). Why I choose this organization? Some reason I can say:
- The codebase is small (but not the scope, for me), it gives me advantage that it is easy to deploy on my local machine.
- It use C as its primary programming language. At this time, I would like to learn about C (Linux was influenced me) on real world application. I was starting to learn C but just on the theory. So, working on Wget2 could help me to learn C with practice.
- The GNU Project reputation is well recognized in Open Source community so working on their project will give me excellent opportunity to learn.
I immediately take a look on their repository. I try to clone their repository
and build on my machine. GNU Wget2 use mailing list as their primary
communication channel, so I sent greeting to them about what project I
interesting at.
The contributor of the project is so friendly. They give me some clues about
what should I do. I should do some microproject work to show effort from me to
understand their workflow and also their codebase. That means, I have to send a
patch (there was Github pull request at this time) to the project. I choose to
work on adding new unit test to function wget_robots_parse()
[0]. Just like
most of open source contribution, it took some process of revision of patch so
it can be merged to upstream. I respect the reviewing process, it help me learn
a lot. Also from now I learn a lot to use GDB (GNU Debugger) to debug
application.
At first I choose to use subject “Design and Implementation of a Framework for
Plugins”. But I rethink again because what I do in micro project is about
Testing. Finally I change my subject so I take “Design and Implementation of
Test Suite Using Libmicrohttpd” as my project. I send my proposal to project’s
mailing list [1]. After some reviewing and revision process, I officially sent
my proposal to GSoC homepage and my proposal was accepted.
The project aim to use Libmicrohttpd as test suite for Wget2. I planned to
complete this by change on function wget_test_start_server()
also
wget_test_stop_server()
from src/libtest.c of Wget2. With this
approach, I don’t need to change existing test suite which call the internal
server code through functions mentioned above.
I’ve count there are 36 test file which use
wget_test_start_server()
. I must ensure all the test passed. And
for installation prerequisite, I must ensure that Libmicrohttpd are included
when building Wget2 binary. Then I need to modify configure.ac. I will give
proper warning about this requirement. There is a section in README.md where I
must explain to user to provide Libmicrohttpd to make all test running
correctly. With Libmicrohttpd I can add new test using feature that not yet
implemented in old server code, but ready on Libmicrohttpd, such as HTTP
authentication and concurrent request checking.
Community Bonding Period
During this period, I spend my time to learn codebase about Wget2 and Libmicrohttpd. I was busy to learning them, that I forget to stay active in community. To prevent mentors give me minus points, I try to work on an issue. I try an easy one, adding enhancement for capabilities of file extended attributes. In short words, Wget2 could save target file’s extended attributes if filesystem support it. It is like the feature on Wget 1.x. To solve this issue, I have to dive a little bit on Wget2 codebase. That’s give me a view about how Wget2 works.
1st Week
There comes the first week of my official coding period. I was work on some
basic tasks for this projects, such as modify the build tools. I have modifed
configure.ac to include Libmicrohttpd into Wget2. I just include the package,
and not adding, modifying or removing anything else yet.
With Libmicrohttpd becomes mandatory package to install before building Wget2
binary, there must be proper warning about this requirement, otherwise the
building process will fail. I have add oneliner information into README.md.
Actually, I was misunderstood about this. Christian Grothoff then give me
suggestion to correct it. Dependency to Libmicrohttpd should not become
mandatory. The solution it to make conditionally-compile and run the tests only
if Libmicrohttpd is present while build Wget2. Libmicrohttpd does the same for
Libcurl. Making the dependency optional also avoids the obvious possibility of
circular dependencies if we ever were to add Libwget2-based tests to
Libmicrohttpd.
Tim Ruhsen also reminds that I should to skip the appropriate tests if
Libmicrohttpd is not installed/ available. I should definitely avoid to use
Wget2 legacy server code in Libwget and focus on client functionality instead.
Because after we successfully integrating Libmicrohttpd, we should remove the
legacy server code. I finally understood what they mean and will fix this the
next week.
On the CI/CD section, I have ensured that all make check passed on several
testing machine including: Debian/GCC, Fedora/Clang, MingW64 and OSX.
Fortunately, most of those OS have provided Libmicrohttpd package on their
respective repository, except MingW64 (the representation of Windows build).
Especially for MingW64 build, because I haven’t found the correct package for
Libmicrohttpd, I include Libmicrohttpd by download the source and compile
manually. What I’ve done is add the following lines on .gitlab-ci.yml:
before_script:
- dnf -y install wget
- wget http://ftp.gnu.org/gnu/libmicrohttpd/libmicrohttpd-0.9.55.tar.gz
- tar zxf libmicrohttpd-0.9.55.tar.gz && cd libmicrohttpd-0.9.55/
- mingw64-configure --prefix=/usr/x86_64-w64-mingw32/sys-root/mingw/
- mingw64-make -j$(nproc)
- mingw64-make -j$(nproc) LOG_COMPILER=wine install
- cd -
I asking if my step was right. Tim give me advice on that MingW64 issue, that I
should provide a script (e.g. in contrib/ folder) that will
downloads/builds/installs Libmicrohttpd. That script then added to the CI runner
YAML file(s) so we can test on MinGW64 as well. This will keep us to stay run as
many tests as possible. He suggest that I should move my MinGW64 build script to
contrib in the end of GSOC as a cleanup.
On the other hand, Evgeny Grin tell me there is a package for Libmicrohttpd on
MinGW64, it provided on Msys2 project and has HTTPS support. Another solution,
is to use W32 binary build provided on GNU mirrors, but it has not HTTPS
support. I keep this information for later decision. For now, I should focus on
the test code first.
2nd Week
Things get more interesting. Because in this period, I will starting to work on
Wget2 main testing function. I started on wget_test_start_server()
.
I must change that function in order to call Libmicrohttpd as service for
wget_test()
. Workflow to resolve this:
- Disable initial process for HTTP server socket.
- Disable
_http_server_thread
, instead call new function which call Libmicrohttpd. - Create
_http_server()
function, wrapper for Libmicrohttpd. There is also functionahc_eco()
which use to create proper HTTP response.
Some issues I found in this period:
- Decide what the best threading model for Libmicrohttpd. I currently using
MHD_USE_INTERNALLY_POLLING_THREAD
which use external select. I still check the comparison with legacy code that use Wget2 APIwget_thread_start
. Darshit Shah give me a clue, that I should choose any mechanism that usesselect()
. Then we can change the threading model at a later stage if it turns out to be a bottleneck.epoll
is Linux-only and evenpoll
isn’t always available, so as long as I choose aselect
based implementation, it should be fine. Evgeny said that I can useMHD_is_feature_supported()
withMHD_FEATURE_POLL
andMHD_FEATURE_EPOLL
to check for supported polling functions. Alternatively, with latest versions of Libmicrohttpd I can useMHD_USE_AUTO
and Libmicrohttpd will chooseselect()
,poll()
or epoll-based polling automatically. -
I make the
http_server_port
still hardcoded. Darshit tell me that this becomes important note. The port should be a random number. Usually, passing 0 in the port number makes the kernel choose an open one for it. Having a randomised port is important to ensure that multiple runs don’t step on each other.
I think still don’t know how to accomplish this. Maybe, it just my understanding that when I pass 0 in MHD port number, the result is still 0. Another approach, when I look into the old code, it generate port number by callingwget_tcp_get_local_port()
. But, I need to callwget_tcp_init()
andwget_tcp_listen()
respectively in order to get proper result. Conclusion, do I need to use existingwget_tcp_get_local_port()
to get the port, or maybe there is a function in Libmicrohttpd to do that.
Tim added that all I need is the socket descriptor. How to callgetsockname()
+getnameinfo()
to retrieve the port number I see inlibwget/net.c
/wget_tcp_get_local_port()
. If Libmicrohttpd doesn’t have such a function, either try to get the socket descriptor or extend Libmicrohttpd with a small function (similar code as inwget_tcp_get_local_port
).
Evgeny give more detailed explanations. There are several ways to resolve it:- Initialise socket externally and start
listen()
on it. I can use any bind and port detection function. Pass socketFD
toMHD_start_daemon()
byMHD_OPTION_LISTEN_SOCKET
. - Use
MHD_start_daemon()
with “0” as port number then useMHD_get_daemon_info()
withMHD_DAEMON_INFO_LISTEN_FD
to get listen socket FD. Use any port detection. - Use
MHD_start_daemon()
with “0” as port number then useMHD_get_daemon_info()
withMHD_DAEMON_INFO_BIND_PORT
to get port number. Works withMHD_VERSION >= 0x00095501
and on platforms that supportgetsockname()
.
I can combine second and third methods.
- Initialise socket externally and start
int port_num;
if(0) {}
#if MHD_VERSION >= 0x00095501
else if (MHD_NO !=
MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
{
const union MHD_DaemonInfo *dinfo;
dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
if (NULL == dinfo || 0 == dinfo->port)
{
/* Insert code to handle error. */
return -1;
}
port_num = (int)dinfo->port;
}
#endif /* MHD_VERSION >= 0x00095501 */
else
{
const union MHD_DaemonInfo *dinfo;
MHD_socket sock_fd;
dinfo = MHD_get_daemon_info (d, MHD_OPTION_LISTEN_FD);
if (NULL == dinfo)
{
/* Insert code to handle error. */
return -1;
}
sock_fd = dinfo->listen_fd;
/* Insert code to use port detection on provided socket. */
}
He add note that socket type used by MHD functions is MHD_socket
.
MHD_socket
is int
on POSIX platforms (Linux, *BSD, Unix, Darwin) and
SOCKET
on Windows platform (excluding Cygwin, where MHD_socket
is int
).
- In
ahc_eco()
of Libmicrohttpd, urls data still using static checking for matching with requested urls. In other word, it’s hardcoded. It needs to be changed to dynamic method to accomodate variadic data. - I still not touched HTTPS yet.
- I ask about what to do with FTP and FTPS functions. Since Libmicrohttpd just provide service for HTTP. I was asking, do we need keep the function for FTP{s}, or removing it. Darshit told me that we keep the FTP code intact for now. Then we should look into different libraries that provide a FTP server in C and try to integrate that into your test suite as well. But for this period, it is out of scope.
- The test was failed when I try to resolve URL with question mark. For example:
/subdir1/subpage1.html?query¶m
, when I debug, it return just/subdir1/subpage1.html
so the result is 404 not found. I also check using logging example source code provided in Libmicrohttpd tutorial. When I access using http client such as Wget2 and Firefox, the result is still the same. The URL result omit the query part. I confirm to Libmicrohttpd side about this, whether it is intended behaviour or not. Christian said it that’s intended, for URL parameters/arguments I need to useMHD_get_connection_values()
withkind=MHD_GET_ARGUMENT_KIND
to inspect them.
3rd Week
Based on feedback from mentors on the previous week, I made some fixes.
- Remove initial process for HTTP server socket.
- Create
_http_server_start()
function, wrapper for Libmicrohttpd. There is also functionanswer_to_connection()
which use to create proper HTTP response. - Use select method
(MHD_USE_SELECT_INTERNALLY)
for threading model in Libmicrohttpd to get better compatibility. http_server_port
seized automatically using Libmicrohttpd function by passingMHD_DAEMON_INFO_BIND_PORT
orMHD_DAEMON_INFO_LISTEN_FD
parameter toMHD_get_daemon_info()
.- Using iteration to parse urls data in
answer_to_connection()
. This guarantee we can pass any variadic data to Libmicrohttpd and prevent segmentation fault. - Fix
answer_to_connection()
function to create proper HTTP response (to deal with parameters and arguments on url, to add proper HTTP headers).
4th Week
Finally it comes to the end of the first period. I report to mentors what I’ve done in this week.
- I have finished modify configure.ac to include Libmicrohttpd into Wget2.
- I have ensured that all make check passed on several testing machine including: Debian/GCC, Fedora/Clang, MingW64 and OSX.
- Started working on
wget_test_start_server()
. - I must ensure that all test suite running correctly. To give better
visualization, I’ve created spreadsheet about summary of test file which use
wget_test_start_server()
. Currently, as far as I know, it reaches 87.5% (28 of 32). I need to complete all test suite reach 100%.
After I send it to the mailing list, I have got many feedback from my mentors for this report.
When Darshit trying to check my code, he has noticed that I was working only
on a single branch and have only one commit until the date. This much work is
not enough for the mid semester evaluation. He asks me to split my work into
smaller commits which are discrete units of work. I follow up this with breaking
down my previous one big commit into smaller ones so it could represent my work
in period of time.
About the commit style, he gave me a clear clue. My commit messages are pretty
hard to read. Also, he asks to maintain the standard style of commit messages.
At Wget, the commit messages are converted into a ChangeLog, hence my message
should be written in that format.
git commit
Commit Message title (60 chars)
[Blank Line]
* file_changed(function_name): Description of change
* file_changed(function_name): Description of change
Any other details I want to write
The commit messages I have written are actually fine, but when it exceeds 60
chars in the first line, I should convert my message to the longer format he
just described.
Another thing, he asks to not force push to my (master) branches. There are
other people that pull from these branches to check in on the code. If I force
push, it messes the repository up for all. Force pushing should only be reserved
as a last resort. However, in this case, when I rewrite the commit messages, I
should force push to my own current branch. That will allow me to maintain that
branch later.
I realize that my commit messages are bad practice indeed. I check it through
git log --pretty=oneline
. I have made some changes about that.
But, when I want to push my changes online, it rejected by git. That’s why I
usually force push. I make sure I just make a changes to my own current branch,
not the master one.
The CI system states failures. He asks whether this is an expected failures. I
said that it not intended to be failure. On my machine which use Ubuntu/GCC, I
can run make check
, and some test could pass. Same like on Gitlab CI that use
Debian/GCC. Based on CI artifacts, most of them failed on make check-valgrind
that I miss to check before. I still find out how to deal with this memory leak
error. Actually, I still stuck in this. Then I ask, how to debug using Valgrind.
I read the error logs and it still it pointed me out of nowhere. He said that I
really should discuss these issues with them more. That is exactly why they are
here to mentor me. Valgrind’s memcheck tool which is what is being used in the
make check-valgrind
is a memory leak checker. He will look a little into it
once he can get the basic tests running on his machine. I asked to be focus on
that first. He said we will look into the memory leaks in a while. Most probably
the leaks happen because either I lost the pointer to some allocated memory or
are using the libmicrohttpd API wrong and forgot to free()
some data structure
returned by it.
I analyze how the Valgrind check was failed. On my system which using
Ubuntu/gcc, some tests are failed, while some not. Through this, I will analyze
based some available pattern. Before I found that Valgrind check error, my work
with test suite stopped at how I must deal with timestamp/modified properties in
test-wget-1
. Another test waiting to finish: test-metalink
(looping
undefinitely), test-i-https
(https implementation) and test-auth-basic
(basic
authentication implementation).
He also sees that ./configure always tries to link Libmicrohttpd. But Wget2
should not depend on that. It should try to add the linker flags only when
trying to compile the test suite. The Wget2 binary should not use that flag. I
realize that was there are flaws in my code. I will figure out after some work
against other test files.
On his local system, not a single test passes. make check -j$(nproc)
causes a
segmentation fault and a simple make check
gets stuck on test-wget-1 and never
progresses ahead. He asks me to fix the branch and/or provide them with
information on how to make the tests work. I look at CI system with Fedora/Clang
and indeed the result is all segmentation faults. I still have no clue about
that. With this case, I cannot claim that I have almost 85% tests passing
already. From his perspective, it is still 0%.
In fact, all the tests that my spread sheet marks are passes, end up causing a
segfault on his system. This seems to stem from the fact that the
http_server_tid
is stored as “0” and I try to call pthread_cancel
on that
value. I asks for more detail information about this. I have use some advice
from Evgeny Grin how to deal with dynamic port. Even when I use hard coded port,
the result is still the same. He explains that he don’t think this is connected
to the port number at all. If it is, then my variable naming scheme requires a
serious change. This issue is happening because I have never stored the tid of
the server in the variable, but I still try to cancel that thread id. After I
take a look on my code again, I realize something wrong. Darshit was right. That
segmentation fault error happened because I forgot to remove
wget_thread_cancel
(http_server_tid
) which is used by the old code at server
termination time. After I remove that line, the CI runner of Fedora/Clang
passes some tests, not all test were resulted segmentation fault (14 of 32
pass, while 4 skipped). The other fail because likely Valgrind check error.
Darshit asks me to make sure that my code works as I have mentioned at least one
day before the deadline for the evaluations. On my Week 3 report I planned to
get 100% of passing all of the test suite. But, until Week 4, my job still not
yet all done (even excluding other error generated outside make check
on
Debian/GCC system). I realized I have missed my (personal) target. Hence, he
asks me where do I think I missed out which caused me to fall behind. In
hindsight, he asks what according to me could have been done better. So, I and
they should be look into this together to see where it can be improved. He asks
what topics were I most stuck on, and why did it take me so much time. Also, if
I stuck on any single point for more than a day, he asks to send they a message.
There is a very good chance that they might be able to help me out and fix it
much faster.
Darshit sends a message to both Libmicrohttpd maintainers, Christian and Evgeny:
that the issues in Valgrind seem to come from within Libmicrohttpd. One of the
blocks reported by Valgrind is:
=26290== 72 bytes in 3 blocks are still reachable in loss record 6 of 6
==26290== at 0x4C2BEEF: malloc (in
/usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==26290== by 0x7E06608: ??? (in /usr/lib/libgcrypt.so.20.1.7)
==26290== by 0x7E077FB: ??? (in /usr/lib/libgcrypt.so.20.1.7)
==26290== by 0x7EBEFB1: ??? (in /usr/lib/libgcrypt.so.20.1.7)
==26290== by 0x7EBF02F: ??? (in /usr/lib/libgcrypt.so.20.1.7)
==26290== by 0x7E0655F: ??? (in /usr/lib/libgcrypt.so.20.1.7)
==26290== by 0x7E06764: ??? (in /usr/lib/libgcrypt.so.20.1.7)
==26290== by 0x6634CF8: ??? (in /usr/lib/libmicrohttpd.so.12.43.0)
==26290== by 0x400F339: call_init.part.0 (in /usr/lib/[1]ld-2.25.so)
==26290== by 0x400F445: _dl_init (in /usr/lib/[2]ld-2.25.so)
==26290== by 0x4000D39: ??? (in /usr/lib/[3]ld-2.25.so)
==26290== by 0x9: ???
So, he guess is that this is some memory that is being allocated by
Libmicrohttpd. The test code does indeed seem to call MHD_stop_daemon()
which
should ideally ensure a clean exit. He asks, where did the implementation go
wrong.
Christian said this is a bit hard to evaluate without the debug symbols. Most
likely it would be a false-positive from a globally allocated buffer of
Libgcrypt’s initialization sequence. He asks to download and install debug
symbols for Libgcrypt and ideally Libmicrohttpd. It is unlikely to be
Libmicrohttpd fault, as except for responses there are no Libmicrohttpd buffers
my code would have to free.
He also asks me to ensure that I have followed the Libmicrohttpd API and
documentation correctly. I have followed Libmicrohttpd API documentation and
tutorial for implementation carefully.
First Period Evaluations
After finish my 4th period report, and discuss with mentors about my
difficulties I found, I also must complete first evaluation report via GSoC web
app. For what I can just remember, it just a dashboard which asks my feedback
about the organization I working with. The real report is actually done via
email.
The other day, GSoC announce the evaluations. The result, I passed the
evaluations, but with warning. Mentors give me an important messages about what
happened and what should I improve. The project is indeed behind schedule. The
mentors still give me a chance by passing me in the hopes that I and they will
be able to improve the communication and get the project back on track. One
major suggestion is that I increase the frequency of my project reports from
weekly to daily. Just a single line at the end of each day stating what I have
done and what are my plans for the next day.
Conclusion
That was though days with my first time period of GSoC. There was many jobs left to be finished in the next period. Mistakes happen, but I was learn a lot from them. Thanks to my mentors who patiently guide me through this process.
Reference(s):
[0] https://github.com/rockdaboot/wget2/pull/155
[1] https://lists.gnu.org/archive/html/bug-wget/2017-03/msg00156.html