diff --git a/.travis.yml b/.travis.yml index cf439b4dec6d88d43f1d9ee9661301ca48e58e73..be87f71cf7ebea7d20e4581f0e169ada0ae6f4fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ env: - LWS_METHOD=nologs CMAKE_ARGS="-DLWS_WITH_NO_LOGS=ON" - LWS_METHOD=smp CMAKE_ARGS="-DLWS_MAX_SMP=32 -DLWS_WITH_MINIMAL_EXAMPLES=1" - LWS_METHOD=nows CMAKE_ARGS="-DLWS_ROLE_WS=0" + - LWS_METHOD=threadpool CMAKE_ARGS="-DLWS_WITH_THREADPOOL=1 -DLWS_WITH_MINIMAL_EXAMPLES=1" os: - linux diff --git a/CMakeLists.txt b/CMakeLists.txt index 51c85c52d36296df136c29d38ad68bf3e167f3ea..bec9b4027b19215978096169e377a16ad8185167 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ option(LWS_WITH_PEER_LIMITS "Track peers and restrict resources a single peer ca option(LWS_WITH_ACCESS_LOG "Support generating Apache-compatible access logs" OFF) option(LWS_WITH_RANGES "Support http ranges (RFC7233)" OFF) option(LWS_WITH_SERVER_STATUS "Support json + jscript server monitoring" OFF) +option(LWS_WITH_THREADPOOL "Managed worker thread pool support (relies on pthreads)" ON) option(LWS_WITH_HTTP_STREAM_COMPRESSION "Support HTTP stream compression" OFF) option(LWS_WITH_HTTP_BROTLI "Also offer brotli http stream compression (requires LWS_WITH_HTTP_STREAM_COMPRESSION)" OFF) option(LWS_WITH_ACME "Enable support for ACME automatic cert acquisition + maintenance (letsencrypt etc)" OFF) @@ -289,6 +290,7 @@ endif() if (WIN32) set(LWS_MAX_SMP 1) +set(LWS_WITH_THREADPOOL 0) endif() @@ -749,6 +751,10 @@ set(SOURCES lib/misc/base64-decode.c lib/misc/lws-ring.c lib/roles/pipe/ops-pipe.c) + +if (LWS_WITH_THREADPOOL AND UNIX AND LWS_HAVE_PTHREAD_H) + list(APPEND SOURCES lib/misc/threadpool/threadpool.c) +endif() if (LWS_ROLE_H1 OR LWS_ROLE_H2) list(APPEND SOURCES diff --git a/README.md b/README.md index d2c67b3a7b00cf35b189c5267c3e19faa8f880b7..0eab09c71e323190ff4a3f6bf31e5cc845637945 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ News ---- +## v3.0.1 released + +See the git log for the list of fixes. + ## v3.0.0 released See the changelog for info https://libwebsockets.org/git/libwebsockets/tree/changelog?h=v3.0-stable diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index 6c6418c0473738ace471d620943660a800f2872f..a8e62901f2282da09aa342343b3294bb7bfba637 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -182,5 +182,6 @@ #cmakedefine LWS_WITH_HTTP_STREAM_COMPRESSION #cmakedefine LWS_WITH_HTTP_BROTLI +#cmakedefine LWS_WITH_THREADPOOL ${LWS_SIZEOFPTR_CODE} diff --git a/doc-assets/threadpool-states.svg b/doc-assets/threadpool-states.svg new file mode 100644 index 0000000000000000000000000000000000000000..024c03f1b46f7200a84ca0b12224bee1375f8399 --- /dev/null +++ b/doc-assets/threadpool-states.svg @@ -0,0 +1,153 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg width="139.96mm" height="203.94mm" version="1.1" viewBox="0 0 139.95773 203.94206" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + <defs> + <marker id="a" overflow="visible" orient="auto"> + <path transform="matrix(-.4 0 0 -.4 -4 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/> + </marker> + <marker id="m" overflow="visible" orient="auto"> + <path transform="matrix(-.4 0 0 -.4 -4 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill="#2a7fff" fill-rule="evenodd" stroke="#2a7fff" stroke-width="1pt"/> + </marker> + <marker id="o" overflow="visible" orient="auto"> + <path transform="matrix(-.4 0 0 -.4 -4 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill="#2a7fff" fill-rule="evenodd" stroke="#2a7fff" stroke-width="1pt"/> + </marker> + <marker id="n" overflow="visible" orient="auto"> + <path transform="matrix(-.4 0 0 -.4 -4 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill="#2a7fff" fill-rule="evenodd" stroke="#2a7fff" stroke-width="1pt"/> + </marker> + <marker id="p" overflow="visible" orient="auto"> + <path transform="matrix(-.4 0 0 -.4 -4 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill="#2a7fff" fill-rule="evenodd" stroke="#2a7fff" stroke-width="1pt"/> + </marker> + <marker id="Arrow1Mend" overflow="visible" orient="auto"> + <path transform="matrix(-.4 0 0 -.4 -4 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/> + </marker> + <marker id="e" overflow="visible" orient="auto"> + <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/> + </marker> + <marker id="f" overflow="visible" orient="auto"> + <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/> + </marker> + <marker id="g" overflow="visible" orient="auto"> + <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/> + </marker> + <marker id="h" overflow="visible" orient="auto"> + <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/> + </marker> + <marker id="b" overflow="visible" orient="auto"> + <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/> + </marker> + <marker id="i" overflow="visible" orient="auto"> + <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/> + </marker> + <marker id="c" overflow="visible" orient="auto"> + <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/> + </marker> + <marker id="j" overflow="visible" orient="auto"> + <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/> + </marker> + <marker id="d" overflow="visible" orient="auto"> + <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/> + </marker> + <marker id="k" overflow="visible" orient="auto"> + <path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/> + </marker> + <filter id="l" x="-.028307" y="-.019087" width="1.0566" height="1.0382" color-interpolation-filters="sRGB"> + <feGaussianBlur stdDeviation="1.5622839"/> + </filter> + </defs> + <metadata> + <rdf:RDF> + <cc:Work rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> + <dc:title/> + </cc:Work> + </rdf:RDF> + </metadata> + <g transform="translate(-63.825 34.781)"> + <g> + <rect x="67.574" y="-31.031" width="132.46" height="196.44" filter="url(#l)"/> + <rect x="67.013" y="-31.592" width="132.46" height="196.44" fill="#fff"/> + <rect x="71.316" y="4.5158" width="60.617" height="156.03" fill="#e3e2db" opacity=".497"/> + <circle cx="101.26" cy="21.51" r="9.1221" fill="#fca" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/> + <text x="101.37344" y="22.928638" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="101.37344" y="22.928638" font-family="'Open Sans'" stroke-width=".53093">queued</tspan></text> + <circle cx="101.06" cy="43.549" r="9.1221" fill="#ff0" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/> + <text x="101.1737" y="44.968067" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="101.1737" y="44.968067" font-family="'Open Sans'" stroke-width=".53093">running</tspan></text> + <circle cx="88.275" cy="111.47" r="9.1221" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/> + <text x="88.389488" y="112.88415" fill="#ffffff" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="88.389488" y="112.88415" fill="#ffffff" font-family="'Open Sans'" stroke-width=".53093">finished</tspan></text> + <circle cx="115.44" cy="111.6" r="9.1221" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/> + <text x="115.55593" y="113.01734" fill="#ffffff" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="115.55593" y="113.01734" fill="#ffffff" font-family="'Open Sans'" stroke-width=".53093">stopped</tspan></text> + <circle cx="101.19" cy="89.093" r="9.1221" fill="#00f" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/> + <text x="101.30688" y="90.511833" fill="#ffffff" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="101.30688" y="90.511833" fill="#ffffff" font-family="'Open Sans'" stroke-width=".53093">stopping</tspan></text> + </g> + <g fill="none" stroke="#000" stroke-width=".98901"> + <path d="m92.669 25.172c-5.6792 3.7073-5.9327 8.623-3.0086 12.864 0.59949 0.92797 1.3564 1.75 2.2096 2.45" marker-end="url(#k)"/> + <path d="m92.794 46.725c-6.9332 3.511-5.3268 11.205-0.79902 15.314" marker-end="url(#d)"/> + <path d="m107.31 95.998c1.5624 2.1075 1.7621 1.7662 4.5277 6.3921" marker-end="url(#j)"/> + <path d="m96.922 73.892c-19.598 6.9719-17.845 24.544-15.314 29.697" marker-end="url(#c)"/> + <path d="m105.58 74.072c3.4663 2.6148 2.6371 4.0982 2.0086 6.4967" marker-end="url(#i)"/> + </g> + <text x="83.188652" y="26.350147" fill="#000000" font-family="'Open Sans'" font-size="1.9194px" letter-spacing="0px" stroke-width=".23992" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="83.188652" y="26.350147">threadpool</tspan><tspan x="83.188652" y="28.74935">worker</tspan><tspan x="83.188652" y="31.148552">thread</tspan><tspan x="83.188652" y="33.547756">takes</tspan><tspan x="83.188652" y="35.946957">task</tspan></text> + <text x="81.767693" y="52.351551" fill="#000000" font-family="'Open Sans'" font-size="1.9194px" letter-spacing="0px" stroke-width=".23992" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="81.767693" y="52.351551">worker</tspan><tspan x="81.767693" y="54.750755">produces</tspan><tspan x="81.767693" y="57.149956">a buffer</tspan><tspan x="81.767693" y="59.54916">of output</tspan></text> + <path d="m109.63 63.431c6.9332-3.511 5.3268-11.205 0.79901-15.314" fill="none" marker-end="url(#b)" stroke="#000" stroke-width=".98901"/> + <g fill="#000000" font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px"> + <text x="85.033684" y="93.276115" font-size="1.9194px" stroke-width=".23992" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="85.033684" y="93.276115">nothing</tspan><tspan x="85.033684" y="95.675316">more</tspan><tspan x="85.033684" y="98.074524">to do</tspan></text> + <text x="120.33315" y="51.474964" font-size="2.1167px" stroke-width=".26458" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="120.33315" y="51.474964">buffer</tspan><tspan x="120.33315" y="54.120796">sent on</tspan><tspan x="120.33315" y="56.766632">and more</tspan><tspan x="120.33315" y="59.412464">to do</tspan></text> + <text x="113.87862" y="74.393372" font-size="2.1167px" stroke-width=".26458" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="113.87862" y="74.393372">problems</tspan><tspan x="113.87862" y="77.039207">sending</tspan></text> + </g> + <circle cx="101.19" cy="66.055" r="9.1221" fill="#f00" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/> + <text x="101.30688" y="67.473564" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="101.30688" y="67.473564" font-family="'Open Sans'" stroke-width=".53093">sync</tspan></text> + <path d="m115.52 120.58c-1.0042 3.6297-3.8472 3.6305-6.9278 6.0658" fill="none" marker-end="url(#h)" stroke="#000" stroke-width=".98901"/> + <path d="m89.258 120.63c1.0042 3.6297 3.8472 3.6305 6.9278 6.0658" fill="none" marker-end="url(#g)" stroke="#000" stroke-width=".98901"/> + <text x="102.9891" y="153.14995" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="102.9891" y="153.14995" font-family="'Open Sans'" stroke-width=".53093">free task</tspan></text> + <path d="m103.43 141.5c-0.56428 2.168-0.208 2.0595-0.42484 6.8954" fill="none" marker-end="url(#f)" stroke="#000" stroke-width=".98901"/> + <circle cx="102.84" cy="133.7" r="9.1221" fill="#f00" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/> + <g fill="#000000" font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px"> + <text x="102.95511" y="135.11937" font-size="4.2475px" stroke-width=".53093" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="102.95511" y="135.11937" font-family="'Open Sans'" stroke-width=".53093">sync</tspan></text> + <text x="91.148003" y="3.3398941" font-size="3.8932px" stroke-width=".48665" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="91.148003" y="3.3398941" font-family="'Open Sans'" stroke-width=".48665">worker thread context</tspan></text> + <text x="158.78787" y="-23.464428" font-size="3.8932px" stroke-width=".48665" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="158.78787" y="-23.464428" font-family="'Open Sans'" stroke-width=".48665">lws service thread context</tspan></text> + </g> + <rect x="135.67" y="-21.491" width="60.617" height="182.23" fill="#9dac93" opacity=".497"/> + <text x="87.216949" y="130.01646" fill="#000000" font-family="'Open Sans'" font-size="1.9194px" letter-spacing="0px" stroke-width=".23992" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="87.216949" y="130.01646">wait until</tspan><tspan x="87.216949" y="132.41566">the lws</tspan><tspan x="87.216949" y="134.81487">service</tspan><tspan x="87.216949" y="137.21407">thread</tspan><tspan x="87.216949" y="139.61328">knows the</tspan><tspan x="87.216949" y="142.01248">task is done</tspan></text> + <path d="m167.23 0.99672c-0.56428 2.168-0.208 2.0595-0.42484 6.8954" fill="none" marker-end="url(#e)" stroke="#000" stroke-width=".98901"/> + <g> + <circle cx="166.68" cy="-6.6945" r="9.1221" fill="#cfa" stroke="#4d4d4d" stroke-linejoin="round" stroke-opacity=".99608" stroke-width=".4711"/> + <text x="166.52914" y="-9.9058332" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1" xml:space="preserve"><tspan x="166.52914" y="-9.9058332" style="line-height:1">new</tspan><tspan x="166.52914" y="-5.6583772" style="line-height:1">wsi on</tspan><tspan x="166.52914" y="-1.4109211" style="line-height:1">mount</tspan></text> + <rect x="153.45" y="8.1222" width="27.384" height="16.007" fill="#37c8ab" opacity=".497"/> + <text x="167.22537" y="13.081109" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1" xml:space="preserve"><tspan x="167.22537" y="13.081109" style="line-height:1">protocol</tspan><tspan x="167.22537" y="17.328566" style="line-height:1">_HTTP</tspan><tspan x="167.22537" y="21.576021" style="line-height:1">callback</tspan></text> + </g> + <path d="m153.38 15.565-42.877 5.5222" fill="none" marker-end="url(#Arrow1Mend)" stroke="#2a7fff" stroke-dasharray="0.26499999, 0.26499999" stroke-width=".265"/> + <text transform="rotate(-6.3716)" x="129.80591" y="31.348564" fill="#0044aa" font-family="'Open Sans'" font-size="2.9008px" letter-spacing="0px" stroke-width=".3626" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="129.80591" y="31.348564" fill="#0044aa" font-family="'Open Sans'" stroke-width=".3626">enqueue threadpool task</tspan></text> + <path d="m110.03 66.524 43.37-14.496" fill="none" marker-end="url(#p)" stroke="#2a7fff" stroke-dasharray="0.26499999, 0.26499999" stroke-width=".265"/> + <g> + <rect x="153.23" y="43.846" width="27.384" height="16.007" fill="#37c8ab" opacity=".497"/> + <text x="167.00159" y="51.049557" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1" xml:space="preserve"><tspan x="167.00159" y="51.049557" style="line-height:1">protocol</tspan><tspan x="167.00159" y="55.297012" style="line-height:1">WRITEABLE</tspan></text> + <text transform="rotate(-17.805)" x="111.54491" y="95.327446" fill="#0044aa" font-family="'Open Sans'" font-size="2.9008px" letter-spacing="0px" stroke-width=".3626" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="111.54491" y="95.327446" fill="#0044aa" font-family="'Open Sans'" stroke-width=".3626">cancel service</tspan></text> + </g> + <path d="m152.72 54.497-43.37 14.496" fill="none" marker-end="url(#n)" stroke="#2a7fff" stroke-dasharray="0.26499999, 0.26499999" stroke-width=".265"/> + <g> + <rect x="153.67" y="114.19" width="27.384" height="16.007" fill="#37c8ab" opacity=".497"/> + <text x="167.45035" y="121.39483" fill="#000000" font-family="'Open Sans'" font-size="4.2475px" letter-spacing="0px" stroke-width=".53093" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1" xml:space="preserve"><tspan x="167.45035" y="121.39483" style="line-height:1">protocol</tspan><tspan x="167.45035" y="125.64229" style="line-height:1">WRITEABLE</tspan></text> + <text transform="rotate(-17.805)" x="89.692116" y="162.38983" fill="#0044aa" font-family="'Open Sans'" font-size="2.9008px" letter-spacing="0px" stroke-width=".3626" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="89.692116" y="162.38983" fill="#0044aa" font-family="'Open Sans'" stroke-width=".3626">cancel service</tspan></text> + </g> + <path d="m111.52 136.63 43.37-14.496" fill="none" marker-end="url(#o)" stroke="#2a7fff" stroke-dasharray="0.26499999, 0.26499999" stroke-width=".265"/> + <path d="m154.21 124.6-43.37 14.496" fill="none" marker-end="url(#m)" stroke="#2a7fff" stroke-dasharray="0.26499999, 0.26499999" stroke-width=".265"/> + <g font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px"> + <text transform="rotate(-17.805)" x="107.68779" y="101.80786" fill="#0044aa" font-size="2.9008px" stroke-width=".3626" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="107.68779" y="101.80786" fill="#0044aa" font-family="'Open Sans'" stroke-width=".3626">lws_threadpool_task_sync</tspan></text> + <text transform="rotate(-17.805)" x="85.189613" y="168.89598" fill="#0044aa" font-size="2.9008px" stroke-width=".3626" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="85.189613" y="168.89598" fill="#0044aa" font-family="'Open Sans'" stroke-width=".3626">lws_threadpool_task_status_wsi</tspan></text> + <text x="132.67136" y="108.40496" fill="#000000" font-size="2.1167px" stroke-width=".26458" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="132.67136" y="108.40496">move to</tspan><tspan x="132.67136" y="111.0508">"done queue"</tspan><tspan x="132.67136" y="113.69662">idling</tspan><tspan x="132.67136" y="116.34246">worker thread</tspan></text> + </g> + <path d="m91.919 68.712c-21.074 4.8881-23.555 52.34-13.084 71.959 2.8493 5.3384 9.0772 11.691 14.142 11.914" fill="none" marker-end="url(#a)" stroke="#000" stroke-dasharray="0.265, 0.265" stroke-width=".265"/> + <g fill="#000000" font-family="'Open Sans'" letter-spacing="0px" text-anchor="middle" word-spacing="0px"> + <text x="79.087029" y="66.727417" font-size="2.1167px" stroke-width=".26458" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="79.087029" y="66.727417">wsi has</tspan><tspan x="79.087029" y="69.373253">unexpect-</tspan><tspan x="79.087029" y="72.019081">edly gone</tspan></text> + <text x="167.50217" y="64.309265" font-size="3.5963px" stroke-width=".44954" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="167.50217" y="64.309265">write the</tspan><tspan x="167.50217" y="68.804657">buffer on</tspan><tspan x="167.50217" y="73.300049">the wsi</tspan></text> + <text x="167.53455" y="136.16283" font-size="3.5963px" stroke-width=".44954" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="167.53455" y="136.16283">acknowledge</tspan><tspan x="167.53455" y="140.65822">the task has</tspan><tspan x="167.53455" y="145.15361">ended</tspan></text> + <text x="100.54455" y="-20.110546" font-size="7.4216px" stroke-width=".9277" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="100.54455" y="-20.110546" font-family="'Open Sans'" stroke-width=".9277">Threadpool</tspan></text> + </g> + <g transform="matrix(1.3705 0 0 1.3705 152.73 -89.601)"> + <path d="m-53.889 51.059c-0.2293-0.20876-0.45859-0.41752-0.68788-0.62628h-2.0842c-0.41073-0.4478-0.80598-0.91066-1.2269-1.3486-0.15635-0.18661-0.4411-0.26749-0.65538-0.13157-0.2532 0.13273-0.37597 0.41669-0.39448 0.69074-0.04758 0.31909 0.05354 0.68674 0.35175 0.85254 0.18832 0.10808 0.49386 0.04671 0.5437-0.19062 0.06699-0.21357-0.06488-0.51518-0.31343-0.50948-0.16072 3e-3 -0.26391 0.28316-0.07717 0.33226 0.08893 0.01617 0.10978-0.27326 0.17002-0.07939 0.0638 0.17507-0.16528 0.31938-0.28293 0.16402-0.213-0.21052-0.14192-0.60391 0.10406-0.75718 0.23529-0.07437 0.39199 0.16171 0.52824 0.31112 0.3873 0.42849 0.77336 0.85822 1.1601 1.2873 0.95481 0.0017 1.9097 0.0034 2.8645 0.0051z"/> + <path d="m-56.225 50.077c0.07138-0.08712 0.14278-0.17424 0.21416-0.26135 0.10648 0.11858 0.21295 0.23715 0.31943 0.35573 0.19118 0.0012 0.38235 0.0025 0.57352 0.0036-0.2069-0.22868-0.41381-0.45737-0.62072-0.68605 0.10043-0.11374 0.20086-0.22747 0.30128-0.34121 0.19601 0.22868 0.39203 0.45737 0.58804 0.68605-0.0012-0.219-0.0025-0.43801-0.0036-0.65701-0.10043-0.11252-0.20085-0.22505-0.30128-0.33758 0.07198-0.10615 0.24907-0.21551 0.08668-0.31451-0.36406-0.40214-0.72811-0.80428-1.0922-1.2064-0.76543-0.0026-1.5317 0.01021-2.2966-0.0021-0.1821-0.01604-0.40898-0.07372-0.45071-0.28286-0.088-0.27195 0.21101-0.59285 0.48564-0.46528 0.18982 0.03674 0.11004 0.42003-0.05779 0.29297 0.17429-0.25602-0.31955-0.22697-0.17976 0.02451 0.09575 0.22004 0.44485 0.25635 0.58255 0.05617 0.14323-0.2308-0.05154-0.4937-0.2705-0.58497-0.21915-0.10466-0.49117-0.07771-0.67434 0.08567-0.27378 0.20864-0.40621 0.61643-0.22752 0.92795 0.104 0.233 0.33446 0.38219 0.58455 0.41121 0.33507 0.04187 0.67431 0.01608 1.0114 0.02326h1.3407c0.25409 0.28192 0.50818 0.56384 0.76228 0.84577-0.22868 0.25288-0.45736 0.50576-0.68605 0.75865-0.16572-0.21381-0.41577-0.37628-0.51666-0.63121-0.08161-0.29871 0.35413-0.53156 0.5618-0.30555 0.19943 0.11035-0.01178 0.46291-0.15577 0.26978 0.1166-0.03951 0.16904-0.22582-0.01437-0.21638-0.21499 0.08183-0.12303 0.40655 0.0643 0.46926 0.17871 0.08651 0.40261-0.07556 0.3799-0.27118 0.01826-0.28278-0.18842-0.5907-0.48713-0.60388-0.31967-0.06227-0.68039 0.12472-0.75854 0.45263-0.10766 0.28228 0.06955 0.56541 0.26324 0.75966 0.22723 0.24585 0.44911 0.49691 0.67408 0.74475z" fill="#f00"/> + <path d="m-54.464 50.361c0.0017-0.54757 0.0034-1.0951 0.0051-1.6427-0.33832-0.38748-0.69457-0.75998-1.021-1.1574-0.10913-0.13496-0.19252-0.29467-0.18614-0.47289-0.0086-0.33136 0.19731-0.65612 0.50556-0.78212 0.23741-0.10513 0.52154-0.11525 0.754 0.0094 0.3243 0.16516 0.47791 0.61109 0.30157 0.93353-0.13519 0.19951-0.48216 0.19542-0.59404-0.0265-0.1079-0.14135-0.10151-0.43084 0.10373-0.47103 0.16696-0.0034 0.20764 0.24106 0.02397 0.25083-0.04085 0.15882 0.28306 0.12232 0.27255-0.04546 0.04138-0.23154-0.21514-0.42013-0.4314-0.37653-0.24357 0.02414-0.45758 0.28096-0.37332 0.52553 0.08871 0.24037 0.30285 0.40102 0.46218 0.59282 0.25613 0.27896 0.51148 0.55865 0.76757 0.83766-0.0017 0.61002-0.0034 1.22-0.0051 1.8301-0.19509-0.0017-0.3902-0.0035-0.58527-0.0051z"/> + </g> + <text x="70.290306" y="-13.629649" fill="#000000" font-family="'Open Sans'" font-size="2.8389px" letter-spacing="0px" stroke-width=".35487" text-align="center" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="70.290306" y="-13.629649" text-align="start">synchronization with the lws service thread</tspan><tspan x="70.290306" y="-10.08099" text-align="start">(syncs to the correct service thread for the wsi)</tspan></text> + </g> +</svg> diff --git a/doc-assets/threadpool.svg b/doc-assets/threadpool.svg new file mode 100644 index 0000000000000000000000000000000000000000..08a4f6a64e395ee6621d7717d5d72e5738741a63 --- /dev/null +++ b/doc-assets/threadpool.svg @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="117.73mm" height="119.98mm" version="1.1" viewBox="0 0 117.72776 119.97711" xmlns="http://www.w3.org/2000/svg"><defs><marker id="b" overflow="visible" orient="auto"><path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/></marker><marker id="c" overflow="visible" orient="auto"><path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/></marker><marker id="a" overflow="visible" orient="auto"><path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/></marker><filter id="d" x="-.052144" y="-.051067" width="1.1043" height="1.1021" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="2.3162866"/></filter></defs><g transform="translate(483.61 -108.01)"><g><rect x="-478.05" y="113.57" width="106.61" height="108.86" filter="url(#d)"/><rect x="-480.29" y="111.33" width="106.61" height="108.86" fill="#f9f9f9"/><rect x="-432.75" y="145.67" width="45.287" height="17.581" fill="#f00" opacity=".497" stroke="#18161a" stroke-linejoin="round" stroke-width=".12217"/><rect x="-432.85" y="168.43" width="45.212" height="45.685" fill="#00f" opacity=".497" stroke="#18161a" stroke-linejoin="round" stroke-width=".19677"/></g><g fill="#e7e0e6" stroke="#18161a" stroke-linejoin="round" stroke-width=".060447"><rect x="-431.73" y="146.79" width="43.364" height="4.4949" opacity=".497"/><rect x="-431.79" y="152.38" width="43.364" height="4.4949" opacity=".497"/><rect x="-431.89" y="169.63" width="43.364" height="4.4949" opacity=".497"/><rect x="-431.96" y="175.21" width="43.364" height="4.4949" opacity=".497"/><rect x="-432.02" y="180.87" width="43.364" height="4.4949" opacity=".497"/><rect x="-432.09" y="186.45" width="43.364" height="4.4949" opacity=".497"/><rect x="-432.16" y="191.85" width="43.364" height="4.4949" opacity=".497"/><rect x="-432.22" y="197.43" width="43.364" height="4.4949" opacity=".497"/><rect x="-431.79" y="157.73" width="43.364" height="4.4949" opacity=".497"/><rect x="-432.29" y="202.83" width="43.364" height="4.4949" opacity=".497"/><rect x="-432.35" y="208.41" width="43.364" height="4.4949" opacity=".497"/></g><text x="-425.05948" y="144.5038" fill="#000000" font-family="'Open Sans'" font-size="2.1167px" letter-spacing="0px" stroke-width=".26458" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-425.05948" y="144.5038" stroke-width=".26458">Worker threads</tspan></text><text x="-427.40665" y="166.8611" fill="#000000" font-family="'Open Sans'" font-size="2.1167px" letter-spacing="0px" stroke-width=".26458" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-427.40665" y="166.8611" stroke-width=".26458">Task queue</tspan></text><path d="m-386.3 172.95c7.3502-6.0322 8.1963-16.693-0.13231-20.373" fill="none" marker-end="url(#a)" stroke="#000" stroke-width=".965"/><rect x="-432.86" y="120.06" width="45.283" height="18.628" fill="#f60" opacity=".497" stroke="#18161a" stroke-linejoin="round" stroke-width=".12575"/><g fill="#e7e0e6" stroke="#18161a" stroke-linejoin="round" stroke-width=".060447"><rect x="-431.87" y="121.3" width="43.364" height="4.4949" opacity=".497"/><rect x="-431.93" y="126.88" width="43.364" height="4.4949" opacity=".497"/><rect x="-432" y="132.54" width="43.364" height="4.4949" opacity=".497"/></g><text x="-426.85422" y="118.53297" fill="#000000" font-family="'Open Sans'" font-size="2.1167px" letter-spacing="0px" stroke-width=".26458" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-426.85422" y="118.53297" stroke-width=".26458">Done queue</tspan></text><path d="m-386.6 149.21c7.3502-6.0322 8.1963-16.693-0.13231-20.373" fill="none" marker-end="url(#c)" stroke="#000" stroke-width=".965"/><g><path d="m-448.78 157.21-2.8648-3.3692 2.7188-3.3826 0.13363 1.5579 11.817 0.23143-4e-3 -1.8179 3.3851 3.7575-3.6277 3.3848-0.13429-1.8176-11.427 0.0273z" fill="#acf"/><text x="-442.99722" y="155.21205" fill="#000000" font-family="'Open Sans'" font-size="3.323px" letter-spacing="0px" stroke-width=".41538" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-442.99722" y="155.21205" stroke-width=".41538">SYNC</tspan></text><rect x="-470.06" y="121.56" width="16.576" height="50.864" fill="#e7e0e6" stroke="#18161a" stroke-linejoin="round" stroke-width=".12572"/><text x="-461.9382" y="144.28072" fill="#000000" font-family="'Open Sans'" font-size="3.323px" letter-spacing="0px" stroke-width=".41538" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-461.9382" y="144.28072">LWS</tspan><tspan x="-461.9382" y="148.43448">Service</tspan><tspan x="-461.9382" y="152.58824">Thread</tspan></text><path d="m-449.01 132.81-2.8648-3.3692 2.7188-3.3826 0.13363 1.5579 11.817 0.23143-4e-3 -1.8179 3.3851 3.7575-3.6277 3.3848-0.13429-1.8176-11.427 0.0273z" fill="#acf"/><text x="-443.23111" y="130.80745" fill="#000000" font-family="'Open Sans'" font-size="3.323px" letter-spacing="0px" stroke-width=".41538" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-443.23111" y="130.80745" stroke-width=".41538">reap</tspan></text><text x="-454.41257" y="178.39418" fill="#000000" font-family="'Open Sans'" font-size="1.9144px" letter-spacing="0px" stroke-width=".23929" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-454.41257" y="178.39418">All communication with tasks happens</tspan><tspan x="-454.41257" y="180.78712">in lws service thread context, via the</tspan><tspan x="-454.41257" y="183.18007">WRITEABLE callback</tspan></text><rect x="-473.25" y="191" width="31.563" height="12.803" fill="#e7e0e6" opacity=".497" stroke="#18161a" stroke-linejoin="round" stroke-width=".087035"/></g><g fill="#000000" font-family="'Open Sans'" letter-spacing="0px" word-spacing="0px"><text x="-469.11374" y="189.37514" font-size="4.2914px" stroke-width=".53643" text-align="center" text-anchor="middle" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-469.11374" y="189.37514" stroke-width=".53643">task</tspan></text><g font-size="2.1167px" stroke-width=".26458"><g text-anchor="middle"><text x="-411.27023" y="124.3083" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.27023" y="124.3083" stroke-width=".26458">task</tspan></text><text x="-411.24612" y="129.95815" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.24612" y="129.95815" stroke-width=".26458">task</tspan></text><text x="-411.32364" y="135.46761" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.32364" y="135.46761" stroke-width=".26458">task</tspan></text><text x="-411.23813" y="149.70833" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.23813" y="149.70833" stroke-width=".26458">task</tspan></text><text x="-411.18332" y="155.39687" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.18332" y="155.39687" stroke-width=".26458">task</tspan></text><text x="-411.15262" y="160.68849" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.15262" y="160.68849" stroke-width=".26458">task</tspan></text><text x="-411.26883" y="172.54805" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.26883" y="172.54805" stroke-width=".26458">task</tspan></text><text x="-411.26883" y="178.22987" text-align="center" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-411.26883" y="178.22987" stroke-width=".26458">task</tspan></text></g><text x="-471.72641" y="194.02657" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-471.72641" y="194.02657"><tspan font-weight="bold" stroke-width=".26458">wsi</tspan> (may be detached)</tspan><tspan x="-471.72641" y="196.67239"><tspan font-weight="bold" stroke-width=".26458">task</tspan> function pointer</tspan><tspan x="-471.72641" y="199.31824"><tspan font-weight="bold" stroke-width=".26458">cleanup</tspan> function pointer</tspan><tspan x="-471.72641" y="201.96407"><tspan font-weight="bold" stroke-width=".26458">user</tspan> private pointer</tspan></text></g><text x="-452.33572" y="213.37485" font-size="5.4153px" stroke-width=".67691" text-align="center" text-anchor="middle" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-452.33572" y="213.37485" stroke-width=".67691">Threadpool</tspan></text></g><g transform="translate(-414.26 162.67)"><path d="m-53.889 51.059c-0.2293-0.20876-0.45859-0.41752-0.68788-0.62628h-2.0842c-0.41073-0.4478-0.80598-0.91066-1.2269-1.3486-0.15635-0.18661-0.4411-0.26749-0.65538-0.13157-0.2532 0.13273-0.37597 0.41669-0.39448 0.69074-0.04758 0.31909 0.05354 0.68674 0.35175 0.85254 0.18832 0.10808 0.49386 0.04671 0.5437-0.19062 0.06699-0.21357-0.06488-0.51518-0.31343-0.50948-0.16072 3e-3 -0.26391 0.28316-0.07717 0.33226 0.08893 0.01617 0.10978-0.27326 0.17002-0.07939 0.0638 0.17507-0.16528 0.31938-0.28293 0.16402-0.213-0.21052-0.14192-0.60391 0.10406-0.75718 0.23529-0.07437 0.39199 0.16171 0.52824 0.31112 0.3873 0.42849 0.77336 0.85822 1.1601 1.2873 0.95481 0.0017 1.9097 0.0034 2.8645 0.0051z"/><path d="m-56.225 50.077c0.07138-0.08712 0.14278-0.17424 0.21416-0.26135 0.10648 0.11858 0.21295 0.23715 0.31943 0.35573 0.19118 0.0012 0.38235 0.0025 0.57352 0.0036-0.2069-0.22868-0.41381-0.45737-0.62072-0.68605 0.10043-0.11374 0.20086-0.22747 0.30128-0.34121 0.19601 0.22868 0.39203 0.45737 0.58804 0.68605-0.0012-0.219-0.0025-0.43801-0.0036-0.65701-0.10043-0.11252-0.20085-0.22505-0.30128-0.33758 0.07198-0.10615 0.24907-0.21551 0.08668-0.31451-0.36406-0.40214-0.72811-0.80428-1.0922-1.2064-0.76543-0.0026-1.5317 0.01021-2.2966-0.0021-0.1821-0.01604-0.40898-0.07372-0.45071-0.28286-0.088-0.27195 0.21101-0.59285 0.48564-0.46528 0.18982 0.03674 0.11004 0.42003-0.05779 0.29297 0.17429-0.25602-0.31955-0.22697-0.17976 0.02451 0.09575 0.22004 0.44485 0.25635 0.58255 0.05617 0.14323-0.2308-0.05154-0.4937-0.2705-0.58497-0.21915-0.10466-0.49117-0.07771-0.67434 0.08567-0.27378 0.20864-0.40621 0.61643-0.22752 0.92795 0.104 0.233 0.33446 0.38219 0.58455 0.41121 0.33507 0.04187 0.67431 0.01608 1.0114 0.02326h1.3407c0.25409 0.28192 0.50818 0.56384 0.76228 0.84577-0.22868 0.25288-0.45736 0.50576-0.68605 0.75865-0.16572-0.21381-0.41577-0.37628-0.51666-0.63121-0.08161-0.29871 0.35413-0.53156 0.5618-0.30555 0.19943 0.11035-0.01178 0.46291-0.15577 0.26978 0.1166-0.03951 0.16904-0.22582-0.01437-0.21638-0.21499 0.08183-0.12303 0.40655 0.0643 0.46926 0.17871 0.08651 0.40261-0.07556 0.3799-0.27118 0.01826-0.28278-0.18842-0.5907-0.48713-0.60388-0.31967-0.06227-0.68039 0.12472-0.75854 0.45263-0.10766 0.28228 0.06955 0.56541 0.26324 0.75966 0.22723 0.24585 0.44911 0.49691 0.67408 0.74475z" fill="#f00"/><path d="m-54.464 50.361c0.0017-0.54757 0.0034-1.0951 0.0051-1.6427-0.33832-0.38748-0.69457-0.75998-1.021-1.1574-0.10913-0.13496-0.19252-0.29467-0.18614-0.47289-0.0086-0.33136 0.19731-0.65612 0.50556-0.78212 0.23741-0.10513 0.52154-0.11525 0.754 0.0094 0.3243 0.16516 0.47791 0.61109 0.30157 0.93353-0.13519 0.19951-0.48216 0.19542-0.59404-0.0265-0.1079-0.14135-0.10151-0.43084 0.10373-0.47103 0.16696-0.0034 0.20764 0.24106 0.02397 0.25083-0.04085 0.15882 0.28306 0.12232 0.27255-0.04546 0.04138-0.23154-0.21514-0.42013-0.4314-0.37653-0.24357 0.02414-0.45758 0.28096-0.37332 0.52553 0.08871 0.24037 0.30285 0.40102 0.46218 0.59282 0.25613 0.27896 0.51148 0.55865 0.76757 0.83766-0.0017 0.61002-0.0034 1.22-0.0051 1.8301-0.19509-0.0017-0.3902-0.0035-0.58527-0.0051z"/></g><path d="m-449.68 167.64 11.817 0.23143-4e-3 -1.8179 3.3851 3.7575-3.6277 3.3848-0.13429-1.8176-13.274 0.23777c0.0499-1.679 7e-3 -2.161 0.0144-4.0462z" fill="#acf"/><text x="-443.22256" y="170.56471" fill="#000000" font-family="'Open Sans'" font-size="3.323px" letter-spacing="0px" stroke-width=".41538" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;line-height:1.25" xml:space="preserve"><tspan x="-443.22256" y="170.56471" stroke-width=".41538">enqueue</tspan></text><g fill="none" stroke="#000"><path d="m-387.42 123.83c1.3484-0.56118 1.6984-1.3714 1.7386-3.1608" marker-end="url(#b)" stroke-width=".965"/><g stroke-width=".26458px"><path d="m-387.65 115.01 1.2568 1.7529"/><path d="m-383.25 120.07 3.4065 1.0253"/><path d="m-383.68 117.3 2.4143-1.2568"/><path d="m-387.19 119.94-1.9513 1.1245"/><path d="m-387.59 118.92-2.8112-0.52916"/><path d="m-384.84 116.57 0.8599-1.6867"/><path d="m-382.92 118.75 2.8773-0.26458"/><path d="m-384.35 120.84 1.3891 2.3151"/><path d="m-387.52 117.66-2.282-1.2237"/></g></g></g></svg> diff --git a/include/libwebsockets.h b/include/libwebsockets.h index 6080c6a302fc43c2432d2a14d83b8ad7e7d11573..ebea945064a681a8702788e54b5dc65c4ad07de1 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -407,6 +407,7 @@ struct lws; #include <libwebsockets/lws-vfs.h> #include <libwebsockets/lws-lejp.h> #include <libwebsockets/lws-stats.h> +#include <libwebsockets/lws-threadpool.h> #if defined(LWS_WITH_TLS) diff --git a/include/libwebsockets/lws-callbacks.h b/include/libwebsockets/lws-callbacks.h index dbf547ad759ccb750e522a3f9150397316360ee3..c0a65cbb8aa568c5dbb913407bc36f99fc7881db 100644 --- a/include/libwebsockets/lws-callbacks.h +++ b/include/libwebsockets/lws-callbacks.h @@ -729,8 +729,7 @@ enum lws_callback_reasons { * these callbacks. The deadline can be continuously extended into the * future by later calls to lws_set_timer_usecs() before the deadline * expires, or cancelled by lws_set_timer_usecs(wsi, -1); - * See the note on lws_set_timer_usecs() about which event loops are - * supported. */ + */ LWS_CALLBACK_EVENT_WAIT_CANCELLED = 71, /**< This is sent to every protocol of every vhost in response diff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h index 76bd3b681a56c05c45b9b128d153792ed059b3dc..44a6ed91bbd46a1653fcf77f329e95ba18c263f6 100644 --- a/include/libwebsockets/lws-context-vhost.h +++ b/include/libwebsockets/lws-context-vhost.h @@ -867,33 +867,5 @@ struct lws_http_mount { void *_unused[2]; /**< dummy */ }; -/** - * lws_http_compression_apply() - apply an http compression transform - * - * \param wsi: the wsi to apply the compression transform to - * \param name: NULL, or the name of the compression transform, eg, "deflate" - * \param p: pointer to pointer to headers buffer - * \param end: pointer to end of headers buffer - * \param decomp: 0 = add compressor to wsi, 1 = add decompressor - * - * This allows transparent compression of dynamically generated HTTP. The - * requested compression (eg, "deflate") is only applied if the client headers - * indicated it was supported (and it has support in lws), otherwise it's a NOP. - * - * If the requested compression method is NULL, then the supported compression - * formats are tried, and for non-decompression (server) mode the first that's - * found on the client's accept-encoding header is chosen. - * - * NOTE: the compression transform, same as h2 support, relies on the user - * code using LWS_WRITE_HTTP and then LWS_WRITE_HTTP_FINAL on the last part - * written. The internal lws fileserving code already does this. - * - * If the library was built without the cmake option - * LWS_WITH_HTTP_STREAM_COMPRESSION set, then a NOP is provided for this api, - * allowing user code to build either way and use compression if available. - */ -LWS_VISIBLE int -lws_http_compression_apply(struct lws *wsi, const char *name, - unsigned char **p, unsigned char *end, char decomp); ///@} ///@} diff --git a/include/libwebsockets/lws-http.h b/include/libwebsockets/lws-http.h index aad6cbe41b86694d8286a8ea68b01b3a055ef228..97a2c712c4c83fa80236c641af3c69a9019da2be 100644 --- a/include/libwebsockets/lws-http.h +++ b/include/libwebsockets/lws-http.h @@ -646,5 +646,34 @@ lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len, */ LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_http_transaction_completed(struct lws *wsi); + +/** + * lws_http_compression_apply() - apply an http compression transform + * + * \param wsi: the wsi to apply the compression transform to + * \param name: NULL, or the name of the compression transform, eg, "deflate" + * \param p: pointer to pointer to headers buffer + * \param end: pointer to end of headers buffer + * \param decomp: 0 = add compressor to wsi, 1 = add decompressor + * + * This allows transparent compression of dynamically generated HTTP. The + * requested compression (eg, "deflate") is only applied if the client headers + * indicated it was supported (and it has support in lws), otherwise it's a NOP. + * + * If the requested compression method is NULL, then the supported compression + * formats are tried, and for non-decompression (server) mode the first that's + * found on the client's accept-encoding header is chosen. + * + * NOTE: the compression transform, same as h2 support, relies on the user + * code using LWS_WRITE_HTTP and then LWS_WRITE_HTTP_FINAL on the last part + * written. The internal lws fileserving code already does this. + * + * If the library was built without the cmake option + * LWS_WITH_HTTP_STREAM_COMPRESSION set, then a NOP is provided for this api, + * allowing user code to build either way and use compression if available. + */ +LWS_VISIBLE int +lws_http_compression_apply(struct lws *wsi, const char *name, + unsigned char **p, unsigned char *end, char decomp); ///@} diff --git a/include/libwebsockets/lws-threadpool.h b/include/libwebsockets/lws-threadpool.h new file mode 100644 index 0000000000000000000000000000000000000000..258ee1abc21ce633773cd520cbc1b506ecd7fa45 --- /dev/null +++ b/include/libwebsockets/lws-threadpool.h @@ -0,0 +1,225 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * included from libwebsockets.h + */ + +/** \defgroup threadpool Threadpool related functions + * ##Threadpool + * \ingroup lwsapi + * + * This allows you to create one or more pool of threads which can run tasks + * associated with a wsi. If the pool is busy, tasks wait on a queue. + * + * Tasks don't have to be atomic, if they will take more than a few tens of ms + * they should return back to the threadpool worker with a return of 0. This + * will allow them to abort cleanly. + */ +//@{ + +struct lws_threadpool; +struct lws_threadpool_task; + +enum lws_threadpool_task_status { + LWS_TP_STATUS_QUEUED, + LWS_TP_STATUS_RUNNING, + LWS_TP_STATUS_SYNCING, + LWS_TP_STATUS_STOPPING, + LWS_TP_STATUS_FINISHED, /* lws_threadpool_task_status() frees task */ + LWS_TP_STATUS_STOPPED, /* lws_threadpool_task_status() frees task */ +}; + +enum lws_threadpool_task_return { + /** Still work to do, just confirming not being stopped */ + LWS_TP_RETURN_CHECKING_IN, + /** Still work to do, enter cond_wait until service thread syncs. This + * is used if you have filled your buffer(s) of data to the service + * thread and are blocked until the service thread completes sending at + * least one. + */ + LWS_TP_RETURN_SYNC, + /** No more work to do... */ + LWS_TP_RETURN_FINISHED, + /** Responding to request to stop */ + LWS_TP_RETURN_STOPPED +}; + +struct lws_threadpool_create_args { + int threads; + int max_queue_depth; +}; + +struct lws_threadpool_task_args { + struct lws *wsi; /**< user must set to wsi task is bound to */ + void *user; /**< user may set (user-private pointer) */ + const char *name; /**< user may set to describe task */ + enum lws_threadpool_task_return (*task)(void *user, + enum lws_threadpool_task_status s); + /**< user must set to actual task function */ + void (*cleanup)(struct lws *wsi, void *user); + /**< socket lifecycle may end while task is not stoppable, so the task + * must be able to detach from any wsi and clean itself up when it does + * stop. If NULL, no cleanup necessary, otherwise point to a user- + * supplied function that destroys the stuff in \p user. + * + * wsi may be NULL on entry, indicating the task got detached due to the + * wsi closing before. + */ +}; + +/** + * lws_threadpool_create() - create a pool of worker threads + * + * \param context: the lws_context the threadpool will exist inside + * \param args: argument struct prepared by caller + * \param format: printf-type format for the task name + * \param ...: printf type args for the task name format + * + * Creates a pool of worker threads with \p threads and a queue of up to + * \p max_queue_depth waiting tasks if all the threads are busy. + * + * Returns NULL if OOM, or a struct lws_threadpool pointer that must be + * destroyed by lws_threadpool_destroy(). + */ +LWS_VISIBLE LWS_EXTERN struct lws_threadpool * +lws_threadpool_create(struct lws_context *context, + const struct lws_threadpool_create_args *args, + const char *format, ...) LWS_FORMAT(3); + +/** + * lws_threadpool_finish() - Stop all pending and running tasks + * + * \param tp: the threadpool object + * + * Marks the threadpool as under destruction. Removes everything from the + * pending queue and completes those tasks as LWS_TP_STATUS_STOPPED. + * + * Running tasks will also get LWS_TP_STATUS_STOPPED as soon as they + * "resurface". + * + * This doesn't reap tasks or free the threadpool, the reaping is done by the + * lws_threadpool_task_status() on the done task. + */ +LWS_VISIBLE LWS_EXTERN void +lws_threadpool_finish(struct lws_threadpool *tp); + +/** + * lws_threadpool_destroy() - Destroy a threadpool + * + * \param tp: the threadpool object + * + * Waits for all worker threads to stop, ends the threads and frees the tp. + */ +LWS_VISIBLE LWS_EXTERN void +lws_threadpool_destroy(struct lws_threadpool *tp); + +/** + * lws_threadpool_enqueue() - Queue the task and run it on a worker thread when possible + * + * \param tp: the threadpool to queue / run on + * \param args: information about what to run + * \param format: printf-type format for the task name + * \param ...: printf type args for the task name format + * + * This asks for a task to run ASAP on a worker thread in threadpool \p tp. + * + * The args defines the wsi, a user-private pointer, a timeout in secs and + * a pointer to the task function. + * + * Returns NULL or an opaque pointer to the queued (or running, or completed) + * task. + * + * Once a task is created and enqueued, it can only be destroyed by calling + * lws_threadpool_task_status() on it after it has reached the state + * LWS_TP_STATUS_FINISHED or LWS_TP_STATUS_STOPPED. + */ +LWS_VISIBLE LWS_EXTERN struct lws_threadpool_task * +lws_threadpool_enqueue(struct lws_threadpool *tp, + const struct lws_threadpool_task_args *args, + const char *format, ...) LWS_FORMAT(3); + +/** + * lws_threadpool_dequeue() - Dequeue or try to stop a running task + * + * \param wsi: the wsi whose current task we want to eliminate + * + * Returns 0 is the task was dequeued or already compeleted, or 1 if the task + * has been asked to stop asynchronously. + * + * This doesn't free the task. It only shortcuts it to state + * LWS_TP_STATUS_STOPPED. lws_threadpool_task_status() must be performed on + * the task separately once it is in LWS_TP_STATUS_STOPPED to free the task. + */ +LWS_VISIBLE LWS_EXTERN int +lws_threadpool_dequeue(struct lws *wsi); + +/** + * lws_threadpool_task_status() - Dequeue or try to stop a running task + * + * \param wsi: the wsi to query the current task of + * \param task: receives a pointer to the opaque task + * \param user: receives a void * pointer to the task user data + * + * This is the equivalent of posix waitpid()... it returns the status of the + * task, and if the task is in state LWS_TP_STATUS_FINISHED or + * LWS_TP_STATUS_STOPPED, frees \p task. If in another state, the task + * continues to exist. + * + * This is designed to be called from the service thread. + * + * Its use is to make sure the service thread has seen the state of the task + * before deleting it. + */ +LWS_VISIBLE LWS_EXTERN enum lws_threadpool_task_status +lws_threadpool_task_status_wsi(struct lws *wsi, + struct lws_threadpool_task **task, void **user); + +/** + * lws_threadpool_task_sync() - Indicate to a stalled task it may continue + * + * \param task: the task to unblock + * \param stop: 0 = run after unblock, 1 = when he unblocks, stop him + * + * Inform the task that the service thread has finished with the shared data + * and that the task, if blocked in LWS_TP_RETURN_SYNC, may continue. + * + * If the lws service context determined that the task must be aborted, it + * should still call this but with stop = 1, causing the task to finish. + */ +LWS_VISIBLE LWS_EXTERN void +lws_threadpool_task_sync(struct lws_threadpool_task *task, int stop); + +/** + * lws_threadpool_dump() - dump the state of a threadpool to the log + * + * \param tp: The threadpool to dump + * + * This locks the threadpool and then dumps the pending queue, the worker + * threads and the done queue, together with time information for how long + * the tasks have been in their current state, how long they have occupied a + * thread, etc. + * + * This only does anything on lws builds with CMAKE_BUILD_TYPE=DEBUG, otherwise + * while it still exists, it's a NOP. + */ + +LWS_VISIBLE LWS_EXTERN void +lws_threadpool_dump(struct lws_threadpool *tp); +//@} diff --git a/include/libwebsockets/lws-timeout-timer.h b/include/libwebsockets/lws-timeout-timer.h index 2631b71cf43ba3a9b6ea1c0bcf6f8b2229dd7f9d..b7f04e5821777b12f32522c9bc6d2ef47d65d206 100644 --- a/include/libwebsockets/lws-timeout-timer.h +++ b/include/libwebsockets/lws-timeout-timer.h @@ -61,6 +61,8 @@ enum pending_timeout { PENDING_TIMEOUT_UDP_IDLE = 26, PENDING_TIMEOUT_CLIENT_CONN_IDLE = 27, PENDING_TIMEOUT_LAGGING = 28, + PENDING_TIMEOUT_THREADPOOL = 29, + PENDING_TIMEOUT_THREADPOOL_TASK = 30, /****** add new things just above ---^ ******/ diff --git a/lib/core/connect.c b/lib/core/connect.c index c5aa1b1dedd7ee098a38060d6561f2dbd6744de0..6e730f3dab544fa40da0280ad0e8ab6b2f0392df 100644 --- a/lib/core/connect.c +++ b/lib/core/connect.c @@ -147,7 +147,7 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i) lwsl_info("%s: protocol binding to %s\n", __func__, local); p = lws_vhost_name_to_protocol(wsi->vhost, local); if (p) - lws_bind_protocol(wsi, p); + lws_bind_protocol(wsi, p, __func__); } /* diff --git a/lib/core/libwebsockets.c b/lib/core/libwebsockets.c index b3a831400af97c9cd5438925fcfea17dcf5d352f..91ce98c400e2b56c400735ae49280f7a4dd8f3c3 100644 --- a/lib/core/libwebsockets.c +++ b/lib/core/libwebsockets.c @@ -537,7 +537,7 @@ lws_remove_child_from_any_parent(struct lws *wsi) } int -lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p) +lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p, const char *reason) { // if (wsi->protocol == p) // return 0; @@ -546,7 +546,7 @@ lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p) if (wsi->protocol && wsi->protocol_bind_balance) { wsi->protocol->callback(wsi, wsi->role_ops->protocol_unbind_cb[!!lwsi_role_server(wsi)], - wsi->user_space, NULL, 0); + wsi->user_space, (void *)reason, 0); wsi->protocol_bind_balance = 0; } if (!wsi->user_space_externally_allocated) @@ -759,7 +759,7 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char * wsi->protocol->callback(wsi, wsi->role_ops->protocol_unbind_cb[ !!lwsi_role_server(wsi)], - wsi->user_space, NULL, 0); + wsi->user_space, (void *)__func__, 0); wsi->protocol_bind_balance = 0; } @@ -794,7 +794,7 @@ just_kill_connection: wsi->protocol->callback(wsi, wsi->role_ops->protocol_unbind_cb[ !!lwsi_role_server(wsi)], - wsi->user_space, NULL, 0); + wsi->user_space, (void *)__func__, 0); wsi->protocol_bind_balance = 0; } diff --git a/lib/core/output.c b/lib/core/output.c index a10d6a3b5c85faab41c7ad482b0b7a2ac08014d6..d43735ade00ef421275e3aabdbbfbc0d0fc3ece2 100644 --- a/lib/core/output.c +++ b/lib/core/output.c @@ -29,7 +29,7 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) struct lws_context *context = lws_get_context(wsi); struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; size_t real_len = len; - unsigned int n; + unsigned int n, m; // lwsl_notice("%s: len %d\n", __func__, (int)len); @@ -104,13 +104,15 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) /* nope, send it on the socket directly */ lws_latency_pre(context, wsi); - n = lws_ssl_capable_write(wsi, buf, n); - lws_latency(context, wsi, "send lws_issue_raw", n, n == len); + m = lws_ssl_capable_write(wsi, buf, n); + lws_latency(context, wsi, "send lws_issue_raw", n, n == m); + + lwsl_info("%s: ssl_capable_write (%d) says %d\n", __func__, n, m); /* something got written, it can have been truncated now */ wsi->could_have_pending = 1; - switch (n) { + switch (m) { case LWS_SSL_CAPABLE_ERROR: /* we're going to close, let close know sends aren't possible */ wsi->socket_is_permanently_unusable = 1; @@ -121,7 +123,7 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) * ie, implying treat it was a truncated send so it gets * retried */ - n = 0; + m = 0; break; } @@ -131,17 +133,17 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) * send in the buflist. */ if (lws_has_buffered_out(wsi)) { - if (n) { - lwsl_info("%p partial adv %d (vs %ld)\n", wsi, n, + if (m) { + lwsl_info("%p partial adv %d (vs %ld)\n", wsi, m, (long)real_len); - lws_buflist_use_segment(&wsi->buflist_out, n); + lws_buflist_use_segment(&wsi->buflist_out, m); } if (!lws_has_buffered_out(wsi)) { lwsl_info("%s: wsi %p: buflist_out flushed\n", __func__, wsi); - n = (int)real_len; + m = (int)real_len; if (lwsi_state(wsi) == LRS_FLUSHING_BEFORE_CLOSE) { lwsl_info("** %p signalling to close now\n", wsi); return -1; /* retry closing now */ @@ -162,7 +164,7 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) /* always callback on writeable */ lws_callback_on_writable(wsi); - return n; + return m; } #if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) @@ -170,9 +172,9 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) lws_callback_on_writable(wsi); #endif - if ((unsigned int)n == real_len) + if (m == real_len) /* what we just sent went out cleanly */ - return n; + return m; /* * We were not able to send everything... and we were not sending from @@ -180,13 +182,13 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) * buffering the unsent remainder on it. * (it will get first priority next time the socket is writable). */ - lwsl_debug("%p new partial sent %d from %lu total\n", wsi, n, + lwsl_debug("%p new partial sent %d from %lu total\n", wsi, m, (unsigned long)real_len); - lws_buflist_append_segment(&wsi->buflist_out, buf + n, real_len - n); + lws_buflist_append_segment(&wsi->buflist_out, buf + m, real_len - m); lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITE_PARTIALS, 1); - lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_B_PARTIALS_ACCEPTED_PARTS, n); + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_B_PARTIALS_ACCEPTED_PARTS, m); #if !defined(LWS_WITH_ESP32) if (lws_wsi_is_udp(wsi)) { diff --git a/lib/core/pollfd.c b/lib/core/pollfd.c index 514ea255e26030aa83b616be5756f5767b1cecc0..f2deea9ffbb6455aaba696d81a941fe3011bbda5 100644 --- a/lib/core/pollfd.c +++ b/lib/core/pollfd.c @@ -474,7 +474,7 @@ lws_same_vh_protocol_insert(struct lws *wsi, int n) { if (wsi->same_vh_protocol_prev || wsi->same_vh_protocol_next) { lws_same_vh_protocol_remove(wsi); - lwsl_notice("Attempted to attach wsi twice to same vh prot\n"); + lwsl_info("Attempted to attach wsi twice to same vh prot\n"); } lws_vhost_lock(wsi->vhost); diff --git a/lib/core/private.h b/lib/core/private.h index 612d02f18588affd514a8626302c9d240d746663..af9741fd7a7352353f5eb712ca5f3aae9602bd2a 100644 --- a/lib/core/private.h +++ b/lib/core/private.h @@ -631,6 +631,11 @@ struct lws_context { struct lws_vhost *vhost_pending_destruction_list; struct lws_plugin *plugin_list; struct lws_deferred_free *deferred_free_list; + +#if defined(LWS_WITH_THREADPOOL) + struct lws_threadpool *tp_list_head; +#endif + #if defined(LWS_WITH_PEER_LIMITS) struct lws_peer **pl_hash_table; struct lws_peer *peer_wait_list; @@ -873,6 +878,10 @@ struct lws { struct lws_dll_lws dll_hrtimer; struct lws_dll_lws dll_buflist; /* guys with pending rxflow */ +#if defined(LWS_WITH_THREADPOOL) + struct lws_threadpool_task *tp_task; +#endif + #if defined(LWS_WITH_PEER_LIMITS) struct lws_peer *peer; #endif @@ -1350,7 +1359,8 @@ int lws_protocol_init(struct lws_context *context); int -lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p); +lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p, + const char *reason); const struct lws_http_mount * lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len); @@ -1488,6 +1498,8 @@ hubbub_error html_parser_cb(const hubbub_token *token, void *pw); #endif +int +lws_threadpool_tsi_context(struct lws_context *context, int tsi); void __lws_remove_from_timeout_list(struct lws *wsi); diff --git a/lib/core/service.c b/lib/core/service.c index 3c4b8f1069a6c888575b010e6d7f59b211799c3c..9e6c5e709086a18a657d82decf1e955e950ccee4 100644 --- a/lib/core/service.c +++ b/lib/core/service.c @@ -99,7 +99,7 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd) wsi->http.comp_ctx.may_have_more) { enum lws_write_protocol wp = LWS_WRITE_HTTP; - lwsl_debug("%s: completing comp partial (buflist_comp %p, may %d)\n", + lwsl_info("%s: completing comp partial (buflist_comp %p, may %d)\n", __func__, wsi->http.comp_ctx.buflist_comp, wsi->http.comp_ctx.may_have_more ); @@ -334,9 +334,10 @@ lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi) { struct lws_context_per_thread *pt = &context->pt[tsi]; - /* Figure out if we really want to wait in poll() - * We only need to wait if really nothing already to do and we have - * to wait for something from network + /* + * Figure out if we really want to wait in poll()... we only need to + * wait if really nothing already to do and we have to wait for + * something from network */ #if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS) /* 1) if we know we are draining rx ext, do not wait in poll */ @@ -347,11 +348,12 @@ lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi) /* 2) if we know we have non-network pending data, do not wait in poll */ if (pt->context->tls_ops && - pt->context->tls_ops->fake_POLLIN_for_buffered) - if (pt->context->tls_ops->fake_POLLIN_for_buffered(pt)) + pt->context->tls_ops->fake_POLLIN_for_buffered && + pt->context->tls_ops->fake_POLLIN_for_buffered(pt)) return 0; - /* 3) If there is any wsi with rxflow buffered and in a state to process + /* + * 3) If there is any wsi with rxflow buffered and in a state to process * it, we should not wait in poll */ @@ -361,6 +363,12 @@ lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi) if (lwsi_state(wsi) != LRS_DEFERRING_ACTION) return 0; + /* + * 4) If any guys with http compression to spill, we shouldn't wait in + * poll but hurry along and service them + */ + + } lws_end_foreach_dll(d); return timeout_ms; diff --git a/lib/misc/threadpool/README.md b/lib/misc/threadpool/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7d51c17e3af3c761f089d55a968ee4344d092361 --- /dev/null +++ b/lib/misc/threadpool/README.md @@ -0,0 +1,175 @@ +## Threadpool + +### Overview + + + +An api that lets you create a pool of worker threads, and a queue of tasks that +are bound to a wsi. Tasks in their own thread synchronize communication to the +lws service thread of the wsi via `LWS_CALLBACK_SERVER_WRITEABLE` and friends. + +Tasks can produce some output, then return that they want to "sync" with the +service thread. That causes a `LWS_CALLBACK_SERVER_WRITEABLE` in the service +thread context, where the output can be consumed, and the task told to continue, +or completed tasks be reaped. + +ALL of the details related to thread synchronization and an associated wsi in +the lws service thread context are handled by the threadpool api, without needing +any pthreads in user code. + +### Example + +https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/ws-server/minimal-ws-server-threadpool + +### Lifecycle considerations + +#### Tasks vs wsi + +Although all tasks start out as being associated to a wsi, in fact the lifetime +of a task and that of the wsi are not necessarily linked. + +You may start a long task, eg, that runs atomically in its thread for 30s, and +at any time the client may close the connection, eg, close a browser window. + +There are arrangements that a task can "check in" periodically with lws to see +if it has been asked to stop, allowing the task lifetime to be related to the +wsi lifetime somewhat, but some tasks are going to be atomic and longlived. + +For that reason, at wsi close an ongoing task can detach from the wsi and +continue until it ends or understands it has been asked to stop. To make +that work, the task is created with a `cleanup` callback that performs any +freeing independent of still having a wsi around to do it... the task takes over +responsibility to free the user pointer on destruction when the task is created. + + + +#### Reaping completed tasks + +Once created, although tasks may run asynchronously, the task itself does not +get destroyed on completion but added to a "done queue". Only when the lws +service thread context queries the task state with `lws_threadpool_task_status()` +may the task be reaped and memory freed. + +This is analogous to unix processes and `wait()`. + +If a task became detached from its wsi, then joining the done queue is enough +to get the task reaped, since there's nobody left any more to synchronize the +reaping with. + +### User interface + +The api is declared at https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-threadpool.h + +#### Threadpool creation / destruction + +The threadpool should be created at program or vhost init using +`lws_threadpool_create()` and destroyed on exit or vhost destruction using +first `lws_threadpool_finish()` and then `lws_threadpool_destroy()`. + +Threadpools should be named, varargs are provided on the create function +to facilite eg, naming the threadpool by the vhost it's associated with. + +Threadpool creation takes an args struct with the following members: + +Member|function +---|--- +threads|The maxiumum number of independent threads in the pool +max_queue_depth|The maximum number of tasks allowed to wait for a place in the pool + +#### Task creation / destruction + +Tasks are created and queued using `lws_threadpool_enqueue()`, this takes an +args struct with the following members + +Member|function +---|--- +wsi|The wsi the task is initially associated with +user|An opaque user-private pointer used for communication with the lws service thread and private state / data +task|A pointer to the function that will run in the pool thread +cleanup|A pointer to a function that will clean up finished or stopped tasks (perhaps freeing user) + +Tasks also should have a name, the creation function again provides varargs +to simplify naming the task with string elements related to who started it +and why. + +#### The task function itself + +The task function receives the task user pointer and the task state. The +possible task states are + +State|Meaning +---|--- +LWS_TP_STATUS_QUEUED|Task is still waiting for a pool thread +LWS_TP_STATUS_RUNNING|Task is supposed to do its work +LWS_TP_STATUS_SYNCING|Task is blocked waiting for sync from lws service thread +LWS_TP_STATUS_STOPPING|Task has been asked to stop but didn't stop yet +LWS_TP_STATUS_FINISHED|Task has reported it has completed +LWS_TP_STATUS_STOPPED|Task has aborted + +The task function will only be told `LWS_TP_STATUS_RUNNING` or +`LWS_TP_STATUS_STOPPING` in its status argument... RUNNING means continue with the +user task and STOPPING means clean up and return `LWS_TP_RETURN_STOPPED`. + +If possible every 100ms or so the task should return `LWS_TP_RETURN_CHECKING_IN` +to allow lws to inform it reasonably quickly that it has been asked to stop +(eg, because the related wsi has closed), or if it can continue. If not +possible, it's okay but eg exiting the application may experience delays +until the running task finishes, and since the wsi may have gone, the work +is wasted. + +The task function may return one of + +Return|Meaning +---|--- +LWS_TP_RETURN_CHECKING_IN|Still wants to run, but confirming nobody asked him to stop. Will be called again immediately with `LWS_TP_STATUS_RUNNING` or `LWS_TP_STATUS_STOPPING` +LWS_TP_RETURN_SYNC|Task wants to trigger a WRITABLE callback and block until lws service thread restarts it with `lws_threadpool_task_sync()` +LWS_TP_RETURN_FINISHED|Task has finished, successfully as far as it goes +LWS_TP_RETURN_STOPPED|Task has finished, aborting in response to a request to stop + +#### Synchronizing + +The task can choose to "SYNC" with the lws service thread, in other words +cause a WRITABLE callback on the associated wsi in the lws service thread +context and block itself until it hears back from there via +`lws_threadpool_task_sync()` to resume the task. + +This is typically used when, eg, the task has filled its buffer, or ringbuffer, +and needs to pause operations until what's done has been sent and some buffer +space is open again. + +In the WRITABLE callback, in lws service thread context, the buffer can be +sent with `lws_write()` and then `lws_threadpool_task_sync()` to allow the task +to fill another buffer and continue that way. + +If the WRITABLE callback determines that the task should stop, it can just call +`lws_threadpool_task_sync()` with the second argument as 1, to force the task +to stop immediately after it resumes. + +#### The cleanup function + +When a finished task is reaped, or a task that become detached from its initial +wsi completes or is stopped, it calls the `.cleanup` function defined in the +task creation args struct to free anything related to the user pointer. + +With threadpool, responsibility for freeing allocations used by the task belongs +strictly with the task, via the `.cleanup` function, once the task has been +enqueued. That's different from a typical non-threadpool protocol where the +wsi lifecycle controls deallocation. This reflects the fact that the task +may outlive the wsi. + +#### Protecting against WRITABLE and / or SYNC duplication + +Care should be taken than data prepared by the task thread in the user priv +memory should only be sent once. For example, after sending data from a user +priv buffer of a given length stored in the priv, zero down the length. + +Task execution and the SYNC writable callbacks are mutually exclusive, so there +is no danger of collision between the task thread and the lws service thread if +the reason for the callback is a SYNC operation from the task thread. + +### Thread overcommit + +If the tasks running on the threads are ultimately network-bound for all or some +of their processing (via the SYNC with the WRITEABLE callback), it's possible +to overcommit the number of threads in the pool compared to the number of +threads the processor has in hardware to get better occupancy in the CPU. diff --git a/lib/misc/threadpool/threadpool.c b/lib/misc/threadpool/threadpool.c new file mode 100644 index 0000000000000000000000000000000000000000..127bf025b571e4a67b97a58d700003775142d42e --- /dev/null +++ b/lib/misc/threadpool/threadpool.c @@ -0,0 +1,979 @@ +/* + * libwebsockets - threadpool api + * + * Copyright (C) 2018 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "core/private.h" + +#include <pthread.h> +#include <string.h> +#include <stdio.h> + +struct lws_threadpool; + +struct lws_threadpool_task { + struct lws_threadpool_task *task_queue_next; + + struct lws_threadpool *tp; + char name[32]; + struct lws_threadpool_task_args args; + + lws_usec_t created; + lws_usec_t acquired; + lws_usec_t done; + lws_usec_t entered_state; + + lws_usec_t acc_running; + lws_usec_t acc_syncing; + + pthread_cond_t wake_idle; + + enum lws_threadpool_task_status status; + + int late_sync_retries; + + char wanted_writeable_cb; +}; + +struct lws_pool { + struct lws_threadpool *tp; + pthread_t thread; + pthread_mutex_t lock; /* part of task wake_idle */ + struct lws_threadpool_task *task; + lws_usec_t acquired; + int worker_index; +}; + +struct lws_threadpool { + pthread_mutex_t lock; /* protects all pool lists */ + pthread_cond_t wake_idle; + struct lws_pool *pool_list; + + struct lws_context *context; + struct lws_threadpool *tp_list; /* context list of threadpools */ + + struct lws_threadpool_task *task_queue_head; + struct lws_threadpool_task *task_done_head; + + char name[32]; + + int threads_in_pool; + int queue_depth; + int done_queue_depth; + int max_queue_depth; + int running_tasks; + + unsigned int destroying:1; +}; + +static int +ms_delta(lws_usec_t now, lws_usec_t then) +{ + return (int)((now - then) / 1000); +} + +static void +us_accrue(lws_usec_t *acc, lws_usec_t then) +{ + lws_usec_t now = lws_now_usecs(); + + *acc += now - then; +} + +static int +pc_delta(lws_usec_t now, lws_usec_t then, lws_usec_t us) +{ + lws_usec_t delta = (now - then) + 1; + + return (int)((us * 100) / delta); +} + +static void +__lws_threadpool_task_dump(struct lws_threadpool_task *task, char *buf, int len) +{ + lws_usec_t now = lws_now_usecs(); + char *end = buf + len - 1; + int syncms = 0, runms = 0; + + if (!task->acquired) { + buf += lws_snprintf(buf, end - buf, + "task: %s, QUEUED queued: %dms", + task->name, ms_delta(now, task->created)); + + return; + } + + if (task->acc_running) + runms = task->acc_running; + + if (task->acc_syncing) + syncms = task->acc_syncing; + + if (!task->done) { + buf += lws_snprintf(buf, end - buf, + "task: %s, ONGOING state %d (%dms) alive: %dms " + "(queued %dms, acquired: %dms, " + "run: %d%%, sync: %d%%)", task->name, task->status, + ms_delta(now, task->entered_state), + ms_delta(now, task->created), + ms_delta(task->acquired, task->created), + ms_delta(now, task->acquired), + pc_delta(now, task->acquired, runms), + pc_delta(now, task->acquired, syncms)); + + return; + } + + buf += lws_snprintf(buf, end - buf, + "task: %s, DONE state %d lived: %dms " + "(queued %dms, on thread: %dms, " + "ran: %d%%, synced: %d%%)", task->name, task->status, + ms_delta(task->done, task->created), + ms_delta(task->acquired, task->created), + ms_delta(task->done, task->acquired), + pc_delta(task->done, task->acquired, runms), + pc_delta(task->done, task->acquired, syncms)); +} + +void +lws_threadpool_dump(struct lws_threadpool *tp) +{ +#if defined(_DEBUG) + struct lws_threadpool_task **c; + char buf[160]; + int n, count; + + pthread_mutex_lock(&tp->lock); /* ======================== tpool lock */ + + lwsl_thread("%s: tp: %s, Queued: %d, Run: %d, Done: %d\n", __func__, + tp->name, tp->queue_depth, tp->running_tasks, + tp->done_queue_depth); + + count = 0; + c = &tp->task_queue_head; + while (*c) { + struct lws_threadpool_task *task = *c; + __lws_threadpool_task_dump(task, buf, sizeof(buf)); + lwsl_thread(" - %s\n", buf); + count++; + + c = &(*c)->task_queue_next; + } + + if (count != tp->queue_depth) + lwsl_err("%s: tp says queue depth %d, but actually %d\n", + __func__, tp->queue_depth, count); + + count = 0; + for (n = 0; n < tp->threads_in_pool; n++) { + struct lws_pool *pool = &tp->pool_list[n]; + struct lws_threadpool_task *task = pool->task; + + if (task) { + __lws_threadpool_task_dump(task, buf, sizeof(buf)); + lwsl_thread(" - worker %d: %s\n", n, buf); + count++; + } + } + + if (count != tp->running_tasks) + lwsl_err("%s: tp says %d running_tasks, but actually %d\n", + __func__, tp->running_tasks, count); + + count = 0; + c = &tp->task_done_head; + while (*c) { + struct lws_threadpool_task *task = *c; + __lws_threadpool_task_dump(task, buf, sizeof(buf)); + lwsl_thread(" - %s\n", buf); + count++; + + c = &(*c)->task_queue_next; + } + + if (count != tp->done_queue_depth) + lwsl_err("%s: tp says done_queue_depth %d, but actually %d\n", + __func__, tp->done_queue_depth, count); + + pthread_mutex_unlock(&tp->lock); /* --------------- tp unlock */ +#endif +} + +static void +state_transition(struct lws_threadpool_task *task, + enum lws_threadpool_task_status status) +{ + task->entered_state = lws_now_usecs(); + task->status = status; +} + +static void +lws_threadpool_task_cleanup_destroy(struct lws_threadpool_task *task) +{ + if (task->args.cleanup) + task->args.cleanup(task->args.wsi, task->args.user); + + if (task->args.wsi) + task->args.wsi->tp_task = NULL; + + lwsl_thread("%s: tp %p: cleaned finished task for wsi %p\n", + __func__, task->tp, task->args.wsi); + + lws_free(task); +} + +static void +__lws_threadpool_reap(struct lws_threadpool_task *task) +{ + struct lws_threadpool_task **c, *t = NULL; + struct lws_threadpool *tp = task->tp; + + /* remove the task from the done queue */ + + c = &tp->task_done_head; + + while (*c) { + if ((*c) == task) { + t = *c; + *c = t->task_queue_next; + t->task_queue_next = NULL; + tp->done_queue_depth--; + + lwsl_thread("%s: tp %s: reaped task wsi %p\n", __func__, + tp->name, task->args.wsi); + + break; + } + c = &(*c)->task_queue_next; + } + + if (!t) + lwsl_err("%s: task %p not in done queue\n", __func__, task); + + /* call the task's cleanup and delete the task itself */ + + lws_threadpool_task_cleanup_destroy(task); +} + +/* + * this gets called from each tsi service context after the service was + * cancelled... we need to ask for the writable callback from the matching + * tsi context for any wsis bound to a worked thread that need it + */ + +int +lws_threadpool_tsi_context(struct lws_context *context, int tsi) +{ + struct lws_threadpool_task **c, *task = NULL; + struct lws_threadpool *tp; + struct lws *wsi; + + lws_context_lock(context, __func__); + + tp = context->tp_list_head; + while (tp) { + int n; + + /* for the running (syncing...) tasks... */ + + for (n = 0; n < tp->threads_in_pool; n++) { + struct lws_pool *pool = &tp->pool_list[n]; + + task = pool->task; + if (!task) + continue; + + wsi = task->args.wsi; + if (!wsi || wsi->tsi != tsi || + !task->wanted_writeable_cb) + continue; + + task->wanted_writeable_cb = 0; + lws_memory_barrier(); + + /* + * finally... we can ask for the callback on + * writable from the correct service thread + * context + */ + + lws_callback_on_writable(wsi); + } + + /* for the done tasks... */ + + c = &tp->task_done_head; + + while (*c) { + task = *c; + wsi = task->args.wsi; + + if (wsi && wsi->tsi == tsi && + task->wanted_writeable_cb) { + + task->wanted_writeable_cb = 0; + lws_memory_barrier(); + + /* + * finally... we can ask for the callback on + * writable from the correct service thread + * context + */ + + lws_callback_on_writable(wsi); + } + + c = &task->task_queue_next; + } + + tp = tp->tp_list; + } + + lws_context_unlock(context); + + return 0; +} + +static int +lws_threadpool_worker_sync(struct lws_pool *pool, + struct lws_threadpool_task *task) +{ + enum lws_threadpool_task_status temp; + struct timespec abstime; + struct lws *wsi; + int tries = 15; + + /* block until writable acknowledges */ + lwsl_debug("%s: %p: LWS_TP_RETURN_SYNC in\n", __func__, task); + pthread_mutex_lock(&pool->lock); /* ======================= pool lock */ + + lwsl_info("%s: %s: task %p (%s): syncing with wsi %p\n", __func__, + pool->tp->name, task, task->name, task->args.wsi); + + temp = task->status; + state_transition(task, LWS_TP_STATUS_SYNCING); + while (tries--) { + wsi = task->args.wsi; + + /* + * if the wsi is no longer attached to this task, there is + * nothing we can sync to usefully. Since the work wants to + * sync, it means we should react to the situation by telling + * the task it can't continue usefully by stopping it. + */ + + if (!wsi) { + lwsl_thread("%s: %s: task %p (%s): No longer bound to any " + "wsi to sync to\n", __func__, pool->tp->name, + task, task->name); + + state_transition(task, LWS_TP_STATUS_STOPPING); + goto done; + } + + /* + * So tries times this is the maximum time between SYNC asking + * for a callback on writable and actually getting it we are + * willing to sit still for. + * + * If it is exceeded, we will stop the task. + */ + abstime.tv_sec = time(NULL) + 2; + abstime.tv_nsec = 0; + + task->wanted_writeable_cb = 1; + lws_memory_barrier(); + + /* + * This will cause lws_threadpool_tsi_context() to get called + * from each tsi service context, where we can safely ask for + * a callback on writeable on the wsi we are associated with. + */ + lws_cancel_service(lws_get_context(wsi)); + + /* + * so the danger here is that we asked for a writable callback + * on the wsi, but for whatever reason, we are never going to + * get one. To avoid deadlocking forever, we allow a set time + * for the sync to happen naturally, otherwise the cond wait + * times out and we stop the task. + */ + + if (pthread_cond_timedwait(&task->wake_idle, &pool->lock, + &abstime) == ETIMEDOUT) { + task->late_sync_retries++; + if (!tries) { + lwsl_err("%s: %s: task %p (%s): SYNC timed out " + "(associated wsi %p)\n", + __func__, pool->tp->name, task, + task->name, task->args.wsi); + + state_transition(task, LWS_TP_STATUS_STOPPING); + goto done; + } + + continue; + } else + break; + } + + if (task->status == LWS_TP_STATUS_SYNCING) + state_transition(task, temp); + + lwsl_debug("%s: %p: LWS_TP_RETURN_SYNC out\n", __func__, task); + +done: + pthread_mutex_unlock(&pool->lock); /* ----------------- - pool unlock */ + + return 0; +} + +static void * +lws_threadpool_worker(void *d) +{ + struct lws_threadpool_task **c, **c2, *task; + struct lws_pool *pool = d; + struct lws_threadpool *tp = pool->tp; + char buf[160]; + + while (!tp->destroying) { + + /* we have no running task... wait and get one from the queue */ + + pthread_mutex_lock(&tp->lock); /* =================== tp lock */ + + /* + * if there's no task already waiting in the queue, wait for + * the wake_idle condition to signal us that might have changed + */ + while (!tp->task_queue_head && !tp->destroying) + pthread_cond_wait(&tp->wake_idle, &tp->lock); + + if (tp->destroying) { + pthread_mutex_unlock(&tp->lock); /* ------ tp unlock */ + continue; + } + + c = &tp->task_queue_head; + c2 = NULL; + task = NULL; + pool->task = NULL; + + /* look at the queue tail */ + while (*c) { + c2 = c; + c = &(*c)->task_queue_next; + } + + /* is there a task at the queue tail? */ + if (c2 && *c2) { + pool->task = task = *c2; + task->acquired = pool->acquired = lws_now_usecs(); + /* remove it from the queue */ + *c2 = task->task_queue_next; + task->task_queue_next = NULL; + tp->queue_depth--; + /* mark it as running */ + state_transition(task, LWS_TP_STATUS_RUNNING); + } + + /* someone else got it first... wait and try again */ + if (!task) { + pthread_mutex_unlock(&tp->lock); /* ------ tp unlock */ + continue; + } + + task->wanted_writeable_cb = 0; + + /* we have acquired a new task */ + + __lws_threadpool_task_dump(task, buf, sizeof(buf)); + + lwsl_thread("%s: %s: worker %d ACQUIRING: %s\n", + __func__, tp->name, pool->worker_index, buf); + tp->running_tasks++; + + pthread_mutex_unlock(&tp->lock); /* --------------- tp unlock */ + + /* + * 1) The task can return with LWS_TP_RETURN_CHECKING_IN to + * "resurface" periodically, and get called again with + * cont = 1 immediately to indicate it is picking up where it + * left off if the task is not being "stopped". + * + * This allows long tasks to respond to requests to stop in + * a clean and opaque way. + * + * 2) The task can return with LWS_TP_RETURN_SYNC to register + * a "callback on writable" request on the service thread and + * block until it hears back from the WRITABLE handler. + * + * This allows the work on the thread to be synchronized to the + * previous work being dispatched cleanly. + * + * 3) The task can return with LWS_TP_RETURN_FINISHED to + * indicate its work is completed nicely. + * + * 4) The task can return with LWS_TP_RETURN_STOPPED to indicate + * it stopped and cleaned up after incomplete work. + */ + + do { + lws_usec_t then; + int n; + + if (tp->destroying || !task->args.wsi) + state_transition(task, LWS_TP_STATUS_STOPPING); + + then = lws_now_usecs(); + n = task->args.task(task->args.user, task->status); + us_accrue(&task->acc_running, then); + switch (n) { + case LWS_TP_RETURN_CHECKING_IN: + /* if not destroying the tp, continue */ + break; + case LWS_TP_RETURN_SYNC: + /* block until writable acknowledges */ + then = lws_now_usecs(); + lws_threadpool_worker_sync(pool, task); + us_accrue(&task->acc_syncing, then); + break; + case LWS_TP_RETURN_FINISHED: + state_transition(task, LWS_TP_STATUS_FINISHED); + break; + case LWS_TP_RETURN_STOPPED: + state_transition(task, LWS_TP_STATUS_STOPPED); + break; + } + } while (task->status == LWS_TP_STATUS_RUNNING); + + pthread_mutex_lock(&tp->lock); /* =================== tp lock */ + + tp->running_tasks--; + + if (pool->task->status == LWS_TP_STATUS_STOPPING) + state_transition(task, LWS_TP_STATUS_STOPPED); + + /* move the task to the done queue */ + + pool->task->task_queue_next = tp->task_done_head; + tp->task_done_head = task; + tp->done_queue_depth++; + pool->task->done = lws_now_usecs(); + + if (!pool->task->args.wsi && + (pool->task->status == LWS_TP_STATUS_STOPPED || + pool->task->status == LWS_TP_STATUS_FINISHED)) { + + __lws_threadpool_task_dump(pool->task, buf, sizeof(buf)); + lwsl_thread("%s: %s: worker %d REAPING: %s\n", + __func__, tp->name, pool->worker_index, + buf); + + /* + * there is no longer any wsi attached, so nothing is + * going to take care of reaping us. So we must take + * care of it ourselves. + */ + __lws_threadpool_reap(pool->task); + } else { + + __lws_threadpool_task_dump(pool->task, buf, sizeof(buf)); + lwsl_thread("%s: %s: worker %d DONE: %s\n", + __func__, tp->name, pool->worker_index, + buf); + + /* signal the associated wsi to take a fresh look at + * task status */ + + if (pool->task->args.wsi) { + task->wanted_writeable_cb = 1; + + lws_cancel_service( + lws_get_context(pool->task->args.wsi)); + } + } + + pool->task = NULL; + pthread_mutex_unlock(&tp->lock); /* --------------- tp unlock */ + } + + /* threadpool is being destroyed */ + + pthread_exit(NULL); + + return NULL; +} + +struct lws_threadpool * +lws_threadpool_create(struct lws_context *context, + const struct lws_threadpool_create_args *args, + const char *format, ...) +{ + struct lws_threadpool *tp; + va_list ap; + int n; + + tp = lws_malloc(sizeof(*tp) + (sizeof(struct lws_pool) * args->threads), + "threadpool alloc"); + if (!tp) + return NULL; + + memset(tp, 0, sizeof(*tp) + (sizeof(struct lws_pool) * args->threads)); + tp->pool_list = (struct lws_pool *)(tp + 1); + tp->max_queue_depth = args->max_queue_depth; + + va_start(ap, format); + n = vsnprintf(tp->name, sizeof(tp->name) - 1, format, ap); + va_end(ap); + + lws_context_lock(context, __func__); + + tp->context = context; + tp->tp_list = context->tp_list_head; + context->tp_list_head = tp; + + lws_context_unlock(context); + + pthread_mutex_init(&tp->lock, NULL); + pthread_cond_init(&tp->wake_idle, NULL); + + for (n = 0; n < args->threads; n++) { + tp->pool_list[n].tp = tp; + tp->pool_list[n].worker_index = n; + pthread_mutex_init(&tp->pool_list[n].lock, NULL); + if (pthread_create(&tp->pool_list[n].thread, NULL, + lws_threadpool_worker, &tp->pool_list[n])) { + lwsl_err("thread creation failed\n"); + } else + tp->threads_in_pool++; + } + + return tp; +} + +void +lws_threadpool_finish(struct lws_threadpool *tp) +{ + struct lws_threadpool_task **c, *task; + + pthread_mutex_lock(&tp->lock); /* ======================== tpool lock */ + + /* nothing new can start, running jobs will abort as STOPPED and the + * pool threads will exit ASAP (they are joined in destroy) */ + tp->destroying = 1; + + /* stop everyone in the pending queue and move to the done queue */ + + c = &tp->task_queue_head; + while (*c) { + task = *c; + *c = task->task_queue_next; + task->task_queue_next = tp->task_done_head; + tp->task_done_head = task; + state_transition(task, LWS_TP_STATUS_STOPPED); + tp->queue_depth--; + tp->done_queue_depth++; + task->done = lws_now_usecs(); + + c = &task->task_queue_next; + } + + pthread_mutex_unlock(&tp->lock); /* -------------------- tpool unlock */ + + pthread_cond_broadcast(&tp->wake_idle); +} + +void +lws_threadpool_destroy(struct lws_threadpool *tp) +{ + struct lws_threadpool_task *task, *next; + struct lws_threadpool **ptp; + void *retval; + int n; + + /* remove us from the context list of threadpools */ + + lws_context_lock(tp->context, __func__); + + ptp = &tp->context->tp_list_head; + while (*ptp) { + if (*ptp == tp) { + *ptp = tp->tp_list; + break; + } + ptp = &(*ptp)->tp_list; + } + + lws_context_unlock(tp->context); + + + pthread_mutex_lock(&tp->lock); /* ======================== tpool lock */ + + tp->destroying = 1; + pthread_cond_broadcast(&tp->wake_idle); + pthread_mutex_unlock(&tp->lock); /* -------------------- tpool unlock */ + + lws_threadpool_dump(tp); + + for (n = 0; n < tp->threads_in_pool; n++) { + task = tp->pool_list[n].task; + + /* he could be sitting waiting for SYNC */ + + if (task != NULL) + pthread_cond_broadcast(&task->wake_idle); + + pthread_join(tp->pool_list[n].thread, &retval); + pthread_mutex_destroy(&tp->pool_list[n].lock); + } + lwsl_info("%s: all threadpools exited\n", __func__); + + task = tp->task_done_head; + while (task) { + next = task->task_queue_next; + lws_threadpool_task_cleanup_destroy(task); + tp->done_queue_depth--; + task = next; + } + + pthread_mutex_destroy(&tp->lock); + + lws_free(tp); +} + +/* + * we want to stop and destroy the task and related priv. The wsi may no + * longer exist. + */ + +int +lws_threadpool_dequeue(struct lws *wsi) +{ + struct lws_threadpool *tp; + struct lws_threadpool_task **c, *task; + int n; + + task = wsi->tp_task; + if (!task) + return 0; + + tp = task->tp; + pthread_mutex_lock(&tp->lock); /* ======================== tpool lock */ + + c = &tp->task_queue_head; + + /* is he queued waiting for a chance to run? Mark him as stopped and + * move him on to the done queue */ + + while (*c) { + if ((*c) == task) { + *c = task->task_queue_next; + task->task_queue_next = tp->task_done_head; + tp->task_done_head = task; + state_transition(task, LWS_TP_STATUS_STOPPED); + tp->queue_depth--; + tp->done_queue_depth++; + task->done = lws_now_usecs(); + + lwsl_debug("%s: tp %p: removed queued task wsi %p\n", + __func__, tp, task->args.wsi); + + break; + } + c = &(*c)->task_queue_next; + } + + /* is he on the done queue? */ + + c = &tp->task_done_head; + while (*c) { + if ((*c) == task) { + *c = task->task_queue_next; + task->task_queue_next = NULL; + lws_threadpool_task_cleanup_destroy(task); + tp->done_queue_depth--; + goto bail; + } + c = &(*c)->task_queue_next; + } + + /* he's not in the queue... is he already running on a thread? */ + + for (n = 0; n < tp->threads_in_pool; n++) { + if (!tp->pool_list[n].task || tp->pool_list[n].task != task) + continue; + + /* + * ensure we don't collide with tests or changes in the + * worker thread + */ + pthread_mutex_lock(&tp->pool_list[n].lock); + + /* + * mark him as having been requested to stop... + * the caller will hear about it in his service thread + * context as a request to close + */ + state_transition(task, LWS_TP_STATUS_STOPPING); + + /* disconnect from wsi, and wsi from task */ + + task->args.wsi->tp_task = NULL; + task->args.wsi = NULL; + + pthread_mutex_unlock(&tp->pool_list[n].lock); + + lwsl_debug("%s: tp %p: request stop running task " + "for wsi %p\n", __func__, tp, task->args.wsi); + + break; + } + + if (n == tp->threads_in_pool) { + /* can't find it */ + lwsl_notice("%s: tp %p: no task for wsi %p, decoupling\n", + __func__, tp, task->args.wsi); + task->args.wsi->tp_task = NULL; + task->args.wsi = NULL; + } + +bail: + pthread_mutex_unlock(&tp->lock); /* -------------------- tpool unlock */ + + return 0; +} + +struct lws_threadpool_task * +lws_threadpool_enqueue(struct lws_threadpool *tp, + const struct lws_threadpool_task_args *args, + const char *format, ...) +{ + struct lws_threadpool_task *task = NULL; + va_list ap; + + if (tp->destroying) + return NULL; + + pthread_mutex_lock(&tp->lock); /* ======================== tpool lock */ + + /* + * if there's room on the queue, the job always goes on the queue + * first, then any free thread may pick it up after the wake_idle + */ + + if (tp->queue_depth == tp->max_queue_depth) { + lwsl_notice("%s: queue reached limit %d\n", __func__, + tp->max_queue_depth); + + goto bail; + } + + /* + * create the task object + */ + + task = lws_malloc(sizeof(*task), __func__); + if (!task) + goto bail; + + memset(task, 0, sizeof(*task)); + pthread_cond_init(&task->wake_idle, NULL); + task->args = *args; + task->tp = tp; + task->created = lws_now_usecs(); + + va_start(ap, format); + vsnprintf(task->name, sizeof(task->name) - 1, format, ap); + va_end(ap); + + /* + * add him on the tp task queue + */ + + task->task_queue_next = tp->task_queue_head; + state_transition(task, LWS_TP_STATUS_QUEUED); + tp->task_queue_head = task; + tp->queue_depth++; + + /* + * mark the wsi itself as depending on this tp (so wsi close for + * whatever reason can clean up) + */ + + args->wsi->tp_task = task; + + lwsl_thread("%s: tp %s: enqueued task %p (%s) for wsi %p, depth %d\n", + __func__, tp->name, task, task->name, args->wsi, + tp->queue_depth); + + /* alert any idle thread there's something new on the task list */ + + lws_memory_barrier(); + pthread_cond_signal(&tp->wake_idle); + +bail: + pthread_mutex_unlock(&tp->lock); /* -------------------- tpool unlock */ + + return task; +} + +/* this should be called from the service thread */ + +enum lws_threadpool_task_status +lws_threadpool_task_status_wsi(struct lws *wsi, + struct lws_threadpool_task **task, void **user) +{ + enum lws_threadpool_task_status status; + struct lws_threadpool *tp; + char buf[160]; + + *task = wsi->tp_task; + if (!*task) + return -1; + + tp = (*task)->tp; + *user = (*task)->args.user; + status = (*task)->status; + + if (status == LWS_TP_STATUS_FINISHED || + status == LWS_TP_STATUS_STOPPED) { + + pthread_mutex_lock(&tp->lock); /* ================ tpool lock */ + __lws_threadpool_task_dump(*task, buf, sizeof(buf)); + lwsl_thread("%s: %s: service thread REAPING: %s\n", + __func__, tp->name, buf); + __lws_threadpool_reap(*task); + lws_memory_barrier(); + pthread_mutex_unlock(&tp->lock); /* ------------ tpool unlock */ + } + + return status; +} + +void +lws_threadpool_task_sync(struct lws_threadpool_task *task, int stop) +{ + lwsl_debug("%s\n", __func__); + + if (stop) + state_transition(task, LWS_TP_STATUS_STOPPING); + + pthread_cond_signal(&task->wake_idle); +} diff --git a/lib/roles/h1/ops-h1.c b/lib/roles/h1/ops-h1.c index f47ae9c7fbb567acdca810b3a6bb63a7a7bb05b3..61910c7c1d0692691b7a7ad6ff51decdb8e42050 100644 --- a/lib/roles/h1/ops-h1.c +++ b/lib/roles/h1/ops-h1.c @@ -42,7 +42,7 @@ lws_read_h1(struct lws *wsi, unsigned char *buf, lws_filepos_t len) lws_filepos_t body_chunk_len; size_t n; - // lwsl_notice("%s: h1 path: wsi state 0x%x\n", __func__, lwsi_state(wsi)); + lwsl_debug("%s: h1 path: wsi state 0x%x\n", __func__, lwsi_state(wsi)); switch (lwsi_state(wsi)) { @@ -225,7 +225,7 @@ ws_mode: break; case LRS_DEFERRING_ACTION: - lwsl_debug("%s: LRS_DEFERRING_ACTION\n", __func__); + lwsl_notice("%s: LRS_DEFERRING_ACTION\n", __func__); break; case LRS_SSL_ACK_PENDING: @@ -543,6 +543,37 @@ rops_handle_POLLIN_h1(struct lws_context_per_thread *pt, struct lws *wsi, } #endif + + /* Priority 2: pre- compression transform */ + +#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) + if (wsi->http.comp_ctx.buflist_comp || + wsi->http.comp_ctx.may_have_more) { + enum lws_write_protocol wp = LWS_WRITE_HTTP; + + lwsl_info("%s: completing comp partial (buflist_comp %p, may %d)\n", + __func__, wsi->http.comp_ctx.buflist_comp, + wsi->http.comp_ctx.may_have_more + ); + + if (wsi->role_ops->write_role_protocol(wsi, NULL, 0, &wp) < 0) { + lwsl_info("%s signalling to close\n", __func__); + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } + lws_callback_on_writable(wsi); + + if (!wsi->http.comp_ctx.buflist_comp && + !wsi->http.comp_ctx.may_have_more && + wsi->http.deferred_transaction_completed) { + wsi->http.deferred_transaction_completed = 0; + if (lws_http_transaction_completed(wsi)) + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } + + return LWS_HPI_RET_HANDLED; + } +#endif + if (lws_is_flowcontrolled(wsi)) /* We cannot deal with any kind of new RX because we are * RX-flowcontrolled. @@ -652,7 +683,7 @@ rops_write_role_protocol_h1(struct lws *wsi, unsigned char *buf, size_t len, if (n) return n; - lwsl_debug("%s: %p: transformed %d bytes to %d " + lwsl_info("%s: %p: transformed %d bytes to %d " "(wp 0x%x, more %d)\n", __func__, wsi, (int)len, (int)o, (int)*wp, wsi->http.comp_ctx.may_have_more); @@ -665,7 +696,7 @@ rops_write_role_protocol_h1(struct lws *wsi, unsigned char *buf, size_t len, * pipelining */ n = lws_snprintf(c, sizeof(c), "%X\x0d\x0a", (int)o); - // lwsl_notice("%s: chunk %s\n", __func__, c); + lwsl_info("%s: chunk (%d) %s", __func__, (int)o, c); out -= n; o += n; memcpy(out, c, n); @@ -673,6 +704,7 @@ rops_write_role_protocol_h1(struct lws *wsi, unsigned char *buf, size_t len, out[o++] = '\x0a'; if (((*wp) & 0x1f) == LWS_WRITE_HTTP_FINAL) { + lwsl_info("%s: final chunk\n", __func__); out[o++] = '0'; out[o++] = '\x0d'; out[o++] = '\x0a'; @@ -902,7 +934,7 @@ rops_perform_user_POLLOUT_h1(struct lws *wsi) wsi->http.comp_ctx.may_have_more) { enum lws_write_protocol wp = LWS_WRITE_HTTP; - lwsl_debug("%s: completing comp partial" + lwsl_info("%s: completing comp partial" "(buflist_comp %p, may %d)\n", __func__, wsi->http.comp_ctx.buflist_comp, wsi->http.comp_ctx.may_have_more); diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c index 5ade1c7f333912d49be0725eddae4891e63ef233..38e53d578d78c5d6a1df09bcfedde700507ca201 100644 --- a/lib/roles/h2/http2.c +++ b/lib/roles/h2/http2.c @@ -1424,6 +1424,10 @@ lws_h2_parse_end_of_frame(struct lws *wsi) } } +#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) + lws_http_compression_validate(h2n->swsi); +#endif + wsi->vhost->conn_stats.h2_trans++; p = lws_hdr_simple_ptr(h2n->swsi, WSI_TOKEN_HTTP_COLON_METHOD); if (!strcmp(p, "POST")) diff --git a/lib/roles/h2/ops-h2.c b/lib/roles/h2/ops-h2.c index c92dd463493dec6ca337ec25e5be6255084af8dd..2c6b2675d8bcbc5ccb05249e13ccf32567cc636c 100644 --- a/lib/roles/h2/ops-h2.c +++ b/lib/roles/h2/ops-h2.c @@ -363,7 +363,7 @@ rops_write_role_protocol_h2(struct lws *wsi, unsigned char *buf, size_t len, size_t olen = len; int n; #if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) - unsigned char mtubuf[1450 + LWS_PRE]; + unsigned char mtubuf[4096 + LWS_PRE]; #endif /* if not in a state to send stuff, then just send nothing */ @@ -396,7 +396,7 @@ rops_write_role_protocol_h2(struct lws *wsi, unsigned char *buf, size_t len, if (n) return n; - lwsl_debug("%s: %p: transformed %d bytes to %d " + lwsl_info("%s: %p: transformed %d bytes to %d " "(wp 0x%x, more %d)\n", __func__, wsi, (int)len, (int)o, (int)*wp, wsi->http.comp_ctx.may_have_more); @@ -776,7 +776,7 @@ lws_h2_bind_for_post_before_action(struct lws *wsi) return 1; } - if (lws_bind_protocol(wsi, pp)) + if (lws_bind_protocol(wsi, pp, __func__)) return 1; } @@ -885,7 +885,7 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi) w->http.comp_ctx.may_have_more) { enum lws_write_protocol wp = LWS_WRITE_HTTP; - lwsl_debug("%s: completing comp partial" + lwsl_info("%s: completing comp partial" "(buflist_comp %p, may %d)\n", __func__, w->http.comp_ctx.buflist_comp, w->http.comp_ctx.may_have_more); diff --git a/lib/roles/http/client/client.c b/lib/roles/http/client/client.c index d80c64a04dd8127b634d1d7909f5628d9b1ccf10..214b7be2e157501dee9a78c9fc8ade5c349162a1 100644 --- a/lib/roles/http/client/client.c +++ b/lib/roles/http/client/client.c @@ -720,7 +720,7 @@ lws_client_interpret_server_handshake(struct lws *wsi) * set-cookie:.test=LWS_1456736240_336776_COOKIE;Max-Age=360000 */ - wsi->http.connection_type = HTTP_CONNECTION_KEEP_ALIVE; + wsi->http.conn_type = HTTP_CONNECTION_KEEP_ALIVE; if (!wsi->client_h2_substream) { p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP); if (wsi->do_ws && !p) { @@ -730,7 +730,7 @@ lws_client_interpret_server_handshake(struct lws *wsi) } if (!p) { p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP1_0); - wsi->http.connection_type = HTTP_CONNECTION_CLOSE; + wsi->http.conn_type = HTTP_CONNECTION_CLOSE; } if (!p) { cce = "HS: URI missing"; @@ -828,7 +828,7 @@ lws_client_interpret_server_handshake(struct lws *wsi) /* if h1 KA is allowed, enable the queued pipeline guys */ if (!wsi->client_h2_alpn && !wsi->client_h2_substream && w == wsi) { /* ie, coming to this for the first time */ - if (wsi->http.connection_type == HTTP_CONNECTION_KEEP_ALIVE) + if (wsi->http.conn_type == HTTP_CONNECTION_KEEP_ALIVE) wsi->keepalive_active = 1; else { /* @@ -916,7 +916,7 @@ lws_client_interpret_server_handshake(struct lws *wsi) wsi->http.rx_content_length; } else /* can't do 1.1 without a content length or chunked */ if (!wsi->chunked) - wsi->http.connection_type = + wsi->http.conn_type = HTTP_CONNECTION_CLOSE; /* @@ -1033,7 +1033,7 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt) return NULL; } - lws_bind_protocol(wsi, pr); + lws_bind_protocol(wsi, pr, __func__); } if ((wsi->protocol->callback)(wsi, LWS_CALLBACK_RAW_ADOPT, diff --git a/lib/roles/http/header.c b/lib/roles/http/header.c index fe41f9c3015cd1492f5680f0d85bd5239c787e64..85939343ab29431a7d4f68740303bd43822e7d25 100644 --- a/lib/roles/http/header.c +++ b/lib/roles/http/header.c @@ -141,6 +141,10 @@ lws_add_http_common_headers(struct lws *wsi, unsigned int code, const char *content_type, lws_filepos_t content_len, unsigned char **p, unsigned char *end) { + const char *ka[] = { "close", "keep-alive" }; + int types[] = { HTTP_CONNECTION_CLOSE, HTTP_CONNECTION_KEEP_ALIVE }, + t = 0; + if (lws_add_http_header_status(wsi, code, p, end)) return 1; @@ -149,16 +153,60 @@ lws_add_http_common_headers(struct lws *wsi, unsigned int code, (int)strlen(content_type), p, end)) return 1; - if (content_len != LWS_ILLEGAL_HTTP_CONTENT_LEN) { - if (lws_add_http_header_content_length(wsi, content_len, p, end)) +#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) + if (!wsi->http.lcs && + (!strncmp(content_type, "text/", 5) || + !strcmp(content_type, "application/javascript") || + !strcmp(content_type, "image/svg+xml"))) + lws_http_compression_apply(wsi, NULL, p, end, 0); +#endif + + /* + * if we decided to compress it, we don't know the content length... + * the compressed data will go out chunked on h1 + */ + if ( +#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) + !wsi->http.lcs && +#endif + content_len != LWS_ILLEGAL_HTTP_CONTENT_LEN) { + if (lws_add_http_header_content_length(wsi, content_len, + p, end)) return 1; } else { - if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION, - (unsigned char *)"close", 5, - p, end)) - return 1; - - wsi->http.connection_type = HTTP_CONNECTION_CLOSE; + /* there was no length... it normally means CONNECTION_CLOSE */ +#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) + + if (!wsi->http2_substream && wsi->http.lcs) { + /* so... + * - h1 connection + * - http compression transform active + * - did not send content length + * + * then mark as chunked... + */ + wsi->http.comp_ctx.chunking = 1; + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_TRANSFER_ENCODING, + (unsigned char *)"chunked", 7, p, end)) + return -1; + + /* ... but h1 compression is chunked, if active we can + * still pipeline + */ + if (wsi->http.lcs && + wsi->http.conn_type == HTTP_CONNECTION_KEEP_ALIVE) + t = 1; + } +#endif + if (!wsi->http2_substream) { + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION, + (unsigned char *)ka[t], + (int)strlen(ka[t]), p, end)) + return 1; + + wsi->http.conn_type = types[t]; + } } return 0; @@ -246,6 +294,7 @@ lws_add_http_header_status(struct lws *wsi, unsigned int _code, end)) return 1; } + headers = wsi->vhost->headers; while (headers) { if (lws_add_http_header_by_name(wsi, @@ -303,9 +352,9 @@ lws_return_http_status(struct lws *wsi, unsigned int code, code == HTTP_STATUS_NOT_FOUND) /* we should do a redirect, and do the 404 there */ if (lws_http_redirect(wsi, HTTP_STATUS_FOUND, - (uint8_t *)wsi->vhost->http.error_document_404, - (int)strlen(wsi->vhost->http.error_document_404), - &p, end) > 0) + (uint8_t *)wsi->vhost->http.error_document_404, + (int)strlen(wsi->vhost->http.error_document_404), + &p, end) > 0) return 0; #endif diff --git a/lib/roles/http/private.h b/lib/roles/http/private.h index f901ea08ee986858c5f4405bed7e2a530440011a..366435396d7dcd7583d1054ade0c04c1d02ecf19 100644 --- a/lib/roles/http/private.h +++ b/lib/roles/http/private.h @@ -39,7 +39,7 @@ enum http_version { HTTP_VERSION_2 }; -enum http_connection_type { +enum http_conn_type { HTTP_CONNECTION_CLOSE, HTTP_CONNECTION_KEEP_ALIVE }; @@ -226,10 +226,11 @@ struct _lws_http_mode_related { #if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) struct lws_compression_support *lcs; lws_comp_ctx_t comp_ctx; + unsigned char comp_accept_mask; #endif enum http_version request_version; - enum http_connection_type connection_type; + enum http_conn_type conn_type; lws_filepos_t tx_content_length; lws_filepos_t tx_content_remain; lws_filepos_t rx_content_length; diff --git a/lib/roles/http/server/parsers.c b/lib/roles/http/server/parsers.c index ca05a783699cfbfa2b78d0da43112e93371c73db..b7dcdb49b5a59db5efcf6994c935aa9a573b060a 100644 --- a/lib/roles/http/server/parsers.c +++ b/lib/roles/http/server/parsers.c @@ -1123,6 +1123,7 @@ excessive: set_parsing_complete: if (ah->ues != URIES_IDLE) goto forbid; + if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) { if (lws_hdr_total_length(wsi, WSI_TOKEN_VERSION)) wsi->rx_frame_type = /* temp for ws version index */ diff --git a/lib/roles/http/server/rewrite.c b/lib/roles/http/server/rewrite.c index 61bb613d902341055cea8ce1601c70efcf27c834..1afcce151a54d3a1fa1162d70554dc9b82d43df2 100644 --- a/lib/roles/http/server/rewrite.c +++ b/lib/roles/http/server/rewrite.c @@ -37,7 +37,7 @@ LWS_EXTERN int lws_rewrite_parse(struct lws_rewrite *r, const unsigned char *in, int in_len) { - if (hubbub_parser_parse_chunk(r->parser, in, in_len) != HUBBUB_OK) + if (r && hubbub_parser_parse_chunk(r->parser, in, in_len) != HUBBUB_OK) return -1; return 0; diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c index e17028a37796bd07ac0d43ca3552085b274a4081..8cff753bade430fc9c525ddcebc45af2b5e1430c 100644 --- a/lib/roles/http/server/server.c +++ b/lib/roles/http/server/server.c @@ -673,7 +673,7 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, const struct lws_protocols *pp = lws_vhost_name_to_protocol( wsi->vhost, m->protocol); - if (lws_bind_protocol(wsi, pp)) + if (lws_bind_protocol(wsi, pp, __func__)) return -1; args.p = (char *)p; args.max_len = lws_ptr_diff(end, p); @@ -887,7 +887,7 @@ int lws_http_action(struct lws *wsi) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - enum http_connection_type connection_type; + enum http_conn_type conn_type; enum http_version request_version; char content_length_str[32]; struct lws_process_html_args args; @@ -971,9 +971,9 @@ lws_http_action(struct lws *wsi) /* HTTP/1.1 defaults to "keep-alive", 1.0 to "close" */ if (request_version == HTTP_VERSION_1_1) - connection_type = HTTP_CONNECTION_KEEP_ALIVE; + conn_type = HTTP_CONNECTION_KEEP_ALIVE; else - connection_type = HTTP_CONNECTION_CLOSE; + conn_type = HTTP_CONNECTION_CLOSE; /* Override default if http "Connection:" header: */ if (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECTION)) { @@ -982,12 +982,12 @@ lws_http_action(struct lws *wsi) WSI_TOKEN_CONNECTION); http_conn_str[sizeof(http_conn_str) - 1] = '\0'; if (!strcasecmp(http_conn_str, "keep-alive")) - connection_type = HTTP_CONNECTION_KEEP_ALIVE; + conn_type = HTTP_CONNECTION_KEEP_ALIVE; else if (!strcasecmp(http_conn_str, "close")) - connection_type = HTTP_CONNECTION_CLOSE; + conn_type = HTTP_CONNECTION_CLOSE; } - wsi->http.connection_type = connection_type; + wsi->http.conn_type = conn_type; } n = wsi->protocol->callback(wsi, LWS_CALLBACK_FILTER_HTTP_CONNECTION, @@ -1040,7 +1040,7 @@ lws_http_action(struct lws *wsi) lwsl_info("no hit\n"); - if (lws_bind_protocol(wsi, &wsi->vhost->protocols[0])) + if (lws_bind_protocol(wsi, &wsi->vhost->protocols[0], "no mount hit")) return 1; lwsi_set_state(wsi, LRS_DOING_TRANSACTION); @@ -1260,7 +1260,7 @@ lws_http_action(struct lws *wsi) return 1; } - if (lws_bind_protocol(wsi, pp)) + if (lws_bind_protocol(wsi, pp, "http action CALLBACK bind")) return 1; args.p = uri_ptr; @@ -1344,7 +1344,9 @@ lws_http_action(struct lws *wsi) lws_vhost_name_to_protocol( wsi->vhost, hit->protocol); - if (lws_bind_protocol(wsi, pp)) + lwsi_set_state(wsi, LRS_DOING_TRANSACTION); + + if (lws_bind_protocol(wsi, pp, "http_action HTTP")) return 1; m = pp->callback(wsi, LWS_CALLBACK_HTTP, @@ -1497,7 +1499,8 @@ raw_transition: lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); lws_bind_protocol(wsi, &wsi->vhost->protocols[ wsi->vhost-> - raw_protocol_index]); + raw_protocol_index], + __func__); lwsl_info("transition to raw vh %s prot %d\n", wsi->vhost->name, wsi->vhost->raw_protocol_index); @@ -1635,6 +1638,10 @@ raw_transition: lwsi_set_state(wsi, LRS_ESTABLISHED); wsi->http.fop_fd = NULL; +#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) + lws_http_compression_validate(wsi); +#endif + lwsl_debug("%s: wsi %p: ah %p\n", __func__, (void *)wsi, (void *)wsi->http.ah); @@ -1737,7 +1744,7 @@ lws_http_transaction_completed(struct lws *wsi) return 0; } - lwsl_info("%s: wsi %p\n", __func__, wsi); + lwsl_debug("%s: wsi %p\n", __func__, wsi); #if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) lws_http_compression_destroy(wsi); @@ -1760,12 +1767,12 @@ lws_http_transaction_completed(struct lws *wsi) if (wsi->seen_zero_length_recv) return 1; - if (wsi->http.connection_type != HTTP_CONNECTION_KEEP_ALIVE) { + if (wsi->http.conn_type != HTTP_CONNECTION_KEEP_ALIVE) { lwsl_info("%s: %p: close connection\n", __func__, wsi); return 1; } - if (lws_bind_protocol(wsi, &wsi->vhost->protocols[0])) + if (lws_bind_protocol(wsi, &wsi->vhost->protocols[0], __func__)) return 1; /* @@ -1804,7 +1811,7 @@ lws_http_transaction_completed(struct lws *wsi) if (wsi->http.ah) { // lws_buflist_describe(&wsi->buflist, wsi); if (!lws_buflist_next_segment_len(&wsi->buflist, NULL)) { - lwsl_info("%s: %p: nothing in buflist so detaching ah\n", + lwsl_debug("%s: %p: nothing in buflist so detaching ah\n", __func__, wsi); lws_header_table_detach(wsi, 1); #ifdef LWS_WITH_TLS @@ -1840,13 +1847,14 @@ lws_http_transaction_completed(struct lws *wsi) if (wsi->http.ah) wsi->http.ah->ues = URIES_IDLE; - //lwsi_set_state(wsi, LRS_ESTABLISHED); + //lwsi_set_state(wsi, LRS_ESTABLISHED); // !!! } else if (lws_buflist_next_segment_len(&wsi->buflist, NULL)) if (lws_header_table_attach(wsi, 0)) lwsl_debug("acquired ah\n"); - lwsl_info("%s: %p: keep-alive await new transaction\n", __func__, wsi); + lwsl_debug("%s: %p: keep-alive await new transaction (state 0x%x)\n", + __func__, wsi, wsi->wsistate); lws_callback_on_writable(wsi); return 0; @@ -2096,11 +2104,6 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, (unsigned char *)cc, cclen, &p, end)) return -1; - if (wsi->http.connection_type == HTTP_CONNECTION_KEEP_ALIVE) - if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION, - (unsigned char *)"keep-alive", 10, &p, end)) - return -1; - if (other_headers) { if ((end - p) < other_headers_len) return -1; @@ -2159,7 +2162,7 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) wsi->http.comp_ctx.may_have_more) { enum lws_write_protocol wp = LWS_WRITE_HTTP; - lwsl_debug("%s: completing comp partial (buflist_comp %p, may %d)\n", + lwsl_info("%s: completing comp partial (buflist_comp %p, may %d)\n", __func__, wsi->http.comp_ctx.buflist_comp, wsi->http.comp_ctx.may_have_more); diff --git a/lib/roles/pipe/ops-pipe.c b/lib/roles/pipe/ops-pipe.c index 27b3739ffbb83beb0ee5d3a7b37ef1ef15d5a8dc..659c9bd9358f7799c54e7e17d4df98d58ce7c8c8 100644 --- a/lib/roles/pipe/ops-pipe.c +++ b/lib/roles/pipe/ops-pipe.c @@ -39,6 +39,18 @@ rops_handle_POLLIN_pipe(struct lws_context_per_thread *pt, struct lws *wsi, if (n < 0) return LWS_HPI_RET_PLEASE_CLOSE_ME; #endif + +#if defined(LWS_WITH_THREADPOOL) + /* + * threadpools that need to call for on_writable callbacks do it by + * marking the task as needing one for its wsi, then cancelling service. + * + * Each tsi will call this to perform the actual callback_on_writable + * from the correct service thread context + */ + lws_threadpool_tsi_context(pt->context, pt->tid); +#endif + /* * the poll() wait, or the event loop for libuv etc is a * process-wide resource that we interrupted. So let every diff --git a/lib/roles/raw-skt/ops-raw-skt.c b/lib/roles/raw-skt/ops-raw-skt.c index d1e0ef0b93542227bd796e9cbd2cb3f01bd48eab..f229493039511f9fc20b39e2509d2d25889478ce 100644 --- a/lib/roles/raw-skt/ops-raw-skt.c +++ b/lib/roles/raw-skt/ops-raw-skt.c @@ -156,11 +156,12 @@ rops_adoption_bind_raw_skt(struct lws *wsi, int type, const char *vh_prot_name) LRS_ESTABLISHED, &role_ops_raw_skt); if (vh_prot_name) - lws_bind_protocol(wsi, wsi->protocol); + lws_bind_protocol(wsi, wsi->protocol, __func__); else /* this is the only time he will transition */ lws_bind_protocol(wsi, - &wsi->vhost->protocols[wsi->vhost->raw_protocol_index]); + &wsi->vhost->protocols[wsi->vhost->raw_protocol_index], + __func__); return 1; /* bound */ } diff --git a/lib/roles/ws/server-ws.c b/lib/roles/ws/server-ws.c index c0d719eb67ed9957186fd5f1f6a41e209a4158b7..8c5f25c99dd2fc7a0a235740a5ff8e37aa46ef4b 100644 --- a/lib/roles/ws/server-ws.c +++ b/lib/roles/ws/server-ws.c @@ -326,7 +326,8 @@ lws_process_ws_upgrade(struct lws *wsi) !strcmp(wsi->vhost->protocols[n].name, protocol_name)) { lws_bind_protocol(wsi, - &wsi->vhost->protocols[n]); + &wsi->vhost->protocols[n], + "ws upgrade select pcol"); hit = 1; break; } @@ -353,7 +354,8 @@ lws_process_ws_upgrade(struct lws *wsi) wsi->vhost->default_protocol_index); n = wsi->vhost->default_protocol_index; lws_bind_protocol(wsi, &wsi->vhost->protocols[ - (int)wsi->vhost->default_protocol_index]); + (int)wsi->vhost->default_protocol_index], + "ws upgrade default pcol"); } /* allocate the ws struct for the wsi */ diff --git a/minimal-examples/ws-server/README.md b/minimal-examples/ws-server/README.md index eb33c7a3c51308cf0aadd9f503a6721300ed6708..b69c1d896447cc95660a4fcd32b9ccb9dcd671eb 100644 --- a/minimal-examples/ws-server/README.md +++ b/minimal-examples/ws-server/README.md @@ -5,6 +5,7 @@ minimal-ws-server-echo|Simple ws server that listens and echos back anything cli minimal-ws-server-pmd-bulk|Simple ws server showing how to pass bulk data with permessage-deflate minimal-ws-server-pmd|Simple ws server with permessage-deflate support minimal-ws-server-ring|Like minimal-ws-server but holds the chat in a multi-tail ringbuffer +minimal-ws-server-threadpool|Demonstrates how to use a worker thread pool with lws minimal-ws-server-threads|Simple ws server where data is produced by different threads minimal-ws-server|Serves an index.html over http that opens a ws shared chat client in a browser diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/CMakeLists.txt b/minimal-examples/ws-server/minimal-ws-server-threadpool/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..951e9f642377ae71d15bd23cd7f02294e1168fc3 --- /dev/null +++ b/minimal-examples/ws-server/minimal-ws-server-threadpool/CMakeLists.txt @@ -0,0 +1,92 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckIncludeFile) +include(CheckCSourceCompiles) + +set(SAMP lws-minimal-ws-server-threadpool) +set(SRCS minimal-ws-server-threadpool.c) + +MACRO(require_pthreads result) + CHECK_INCLUDE_FILE(pthread.h LWS_HAVE_PTHREAD_H) + if (NOT LWS_HAVE_PTHREAD_H) + if (LWS_WITH_MINIMAL_EXAMPLES) + set(result 0) + else() + message(FATAL_ERROR "threading support requires pthreads") + endif() + endif() +ENDMACRO() + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + + endif() +ENDMACRO() + +set(requirements 1) +require_pthreads(requirements) +require_lws_config(LWS_ROLE_WS 1 requirements) +require_lws_config(LWS_WITHOUT_SERVER 0 requirements) +require_lws_config(LWS_WITH_THREADPOOL 1 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared pthread) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets pthread) + endif() +endif() diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/README.md b/minimal-examples/ws-server/minimal-ws-server-threadpool/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c8a91df40b9182e7f4536809c3376bd7c119ac32 --- /dev/null +++ b/minimal-examples/ws-server/minimal-ws-server-threadpool/README.md @@ -0,0 +1,26 @@ +# lws minimal ws server (threadpool) + +## build + +``` + $ cmake . && make +``` + +Pthreads is required on your system. + +This demonstrates how to cleanly assign tasks bound to a wsi to a thread pool, +with a queue if the pool is occupied. + +It creates a threadpool with 3 worker threads and a maxiumum queue size of 4. + +The web page at http://localhost:7681 then starts up 8 x ws connections. + +## usage + +``` + $ ./lws-minimal-ws-server-threadpool +[2018/03/13 13:09:52:2208] USER: LWS minimal ws server + threadpool | visit http://localhost:7681 +[2018/03/13 13:09:52:2365] NOTICE: Creating Vhost 'default' port 7681, 2 protocols, IPv6 off +``` + + diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/minimal-ws-server-threadpool.c b/minimal-examples/ws-server/minimal-ws-server-threadpool/minimal-ws-server-threadpool.c new file mode 100644 index 0000000000000000000000000000000000000000..3b4ad7a85b25d1283a89e8abe6c813a33b05dbbe --- /dev/null +++ b/minimal-examples/ws-server/minimal-ws-server-threadpool/minimal-ws-server-threadpool.c @@ -0,0 +1,127 @@ +/* + * lws-minimal-ws-server=threadpool + * + * Copyright (C) 2018 Andy Green <andy@warmcat.com> + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * This demonstrates a minimal ws server that can cooperate with + * other threads cleanly. Two other threads are started, which fill + * a ringbuffer with strings at 10Hz. + * + * The actual work and thread spawning etc are done in the protocol + * implementation in protocol_lws_minimal.c. + * + * To keep it simple, it serves stuff in the subdirectory "./mount-origin" of + * the directory it was started in. + * You can change that by changing mount.origin. + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <pthread.h> + +#define LWS_PLUGIN_STATIC +#include "protocol_lws_minimal_threadpool.c" + +static struct lws_protocols protocols[] = { + { "http", lws_callback_http_dummy, 0, 0 }, + LWS_PLUGIN_PROTOCOL_MINIMAL, + { NULL, NULL, 0, 0 } /* terminator */ +}; + +static int interrupted; + +static const struct lws_http_mount mount = { + /* .mount_next */ NULL, /* linked-list "next" */ + /* .mountpoint */ "/", /* mountpoint URL */ + /* .origin */ "./mount-origin", /* serve from dir */ + /* .def */ "index.html", /* default filename */ + /* .protocol */ NULL, + /* .cgienv */ NULL, + /* .extra_mimetypes */ NULL, + /* .interpret */ NULL, + /* .cgi_timeout */ 0, + /* .cache_max_age */ 0, + /* .auth_mask */ 0, + /* .cache_reusable */ 0, + /* .cache_revalidate */ 0, + /* .cache_intermediaries */ 0, + /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */ + /* .mountpoint_len */ 1, /* char count */ + /* .basic_auth_login_file */ NULL, +}; + +/* + * This demonstrates how to pass a pointer into a specific protocol handler + * running on a specific vhost. In this case, it's our default vhost and + * we pass the pvo named "config" with the value a const char * "myconfig". + * + * This is the preferred way to pass configuration into a specific vhost + + * protocol instance. + */ + +static const struct lws_protocol_vhost_options pvo_ops = { + NULL, + NULL, + "config", /* pvo name */ + (void *)"myconfig" /* pvo value */ +}; + +static const struct lws_protocol_vhost_options pvo = { + NULL, /* "next" pvo linked-list */ + &pvo_ops, /* "child" pvo linked-list */ + "lws-minimal", /* protocol name we belong to on this vhost */ + "" /* ignored */ +}; + +void sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + const char *p; + int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE + /* for LLL_ verbosity above NOTICE to be built into lws, + * lws must have been configured and built with + * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ + /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ + /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ + /* | LLL_DEBUG */; + + signal(SIGINT, sigint_handler); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + lwsl_user("LWS minimal ws server + threadpool | visit http://localhost:7681\n"); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + info.port = 7681; + info.mounts = &mount; + info.protocols = protocols; + info.pvo = &pvo; /* per-vhost options */ + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* start the threads that create content */ + + while (!interrupted) + if (lws_service(context, 1000)) + interrupted = 1; + + lws_context_destroy(context); + + return 0; +} diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/favicon.ico b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c0cc2e3dff34012ba3d4a7848a7ed17579788ec5 Binary files /dev/null and b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/favicon.ico differ diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/index.html b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/index.html new file mode 100644 index 0000000000000000000000000000000000000000..47fb96b7fbc6193f1dc73626c5724b28eb90be1a --- /dev/null +++ b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/index.html @@ -0,0 +1,97 @@ +<meta charset="UTF-8"> +<html> + <body> + <img src="libwebsockets.org-logo.png"><br> + + <b>Minimal ws server threadpool example</b>.<br> + 8 x ws connections are opened back to the example server.<br> + There are three threads in the pool to service them, the<br> + remainder are queued until a thread in the pool is free.<p> + The textarea show the last 50 lines received. + <br> + <br> + <textarea id=r readonly cols=40 rows=50></textarea><br> + </body> + + +<script> + +var head = 0, tail = 0, ring = new Array(); + +function get_appropriate_ws_url(extra_url) +{ + var pcol; + var u = document.URL; + + /* + * We open the websocket encrypted if this page came on an + * https:// url itself, otherwise unencrypted + */ + + if (u.substring(0, 5) == "https") { + pcol = "wss://"; + u = u.substr(8); + } else { + pcol = "ws://"; + if (u.substring(0, 4) == "http") + u = u.substr(7); + } + + u = u.split('/'); + + /* + "/xxx" bit is for IE10 workaround */ + + return pcol + u[0] + "/" + extra_url; +} + +function new_ws(urlpath, protocol) +{ + if (typeof MozWebSocket != "undefined") + return new MozWebSocket(urlpath, protocol); + + return new WebSocket(urlpath, protocol); +} + +var n, wsa = new Array, alive = 0; + +for (n = 0; n < 8; n++) { + + ws = new_ws(get_appropriate_ws_url(""), "lws-minimal"); + wsa.push(ws); + try { + ws.onopen = function() { + document.getElementById("r").disabled = 0; + alive++; + } + + ws.onmessage =function got_packet(msg) { + var n, s = ""; + + ring[head] = msg.data + "\n"; + head = (head + 1) % 50; + if (tail == head) + tail = (tail + 1) % 50; + + n = tail; + do { + s = s + ring[n]; + n = (n + 1) % 50; + } while (n != head); + + document.getElementById("r").value = s; + document.getElementById("r").scrollTop = + document.getElementById("r").scrollHeight; + } + + ws.onclose = function(){ + if (--alive == 0) + document.getElementById("r").disabled = 1; + } + } catch(exception) { + alert('<p>Error' + exception); + } +} + +</script> +</html> + diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/libwebsockets.org-logo.png b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/libwebsockets.org-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2060a10c936a0959f2a5c3a6b7fa60ac324f1a95 Binary files /dev/null and b/minimal-examples/ws-server/minimal-ws-server-threadpool/mount-origin/libwebsockets.org-logo.png differ diff --git a/minimal-examples/ws-server/minimal-ws-server-threadpool/protocol_lws_minimal_threadpool.c b/minimal-examples/ws-server/minimal-ws-server-threadpool/protocol_lws_minimal_threadpool.c new file mode 100644 index 0000000000000000000000000000000000000000..c03888987f7f67e88f0512e7dc401922aebe6134 --- /dev/null +++ b/minimal-examples/ws-server/minimal-ws-server-threadpool/protocol_lws_minimal_threadpool.c @@ -0,0 +1,343 @@ +/* + * ws protocol handler plugin for "lws-minimal" demonstrating lws threadpool + * + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * The main reason some things are as they are is that the task lifecycle may + * be unrelated to the wsi lifecycle that queued that task. + * + * Consider the task may call an external library and run for 30s without + * "checking in" to see if it should stop. The wsi that started the task may + * have closed at any time before the 30s are up, with the browser window + * closing or whatever. + * + * So data shared between the asynchronous task and the wsi must have its + * lifecycle determined by the task, not the wsi. That means a separate struct + * that can be freed by the task. + * + * In the case the wsi outlives the task, the tasks do not get destroyed until + * the service thread has called lws_threadpool_task_status() on the completed + * task. So there is no danger of the shared task private data getting randomly + * freed. + */ + +#if !defined (LWS_PLUGIN_STATIC) +#define LWS_DLL +#define LWS_INTERNAL +#include <libwebsockets.h> +#endif + +#include <string.h> + +struct per_vhost_data__minimal { + struct lws_threadpool *tp; + const char *config; +}; + +struct task_data { + char result[64]; + + uint64_t pos, end; +}; + +/* + * Create the private data for the task + * + * Notice we hand over responsibility for the cleanup and freeing of the + * allocated task_data to the threadpool, because the wsi it was originally + * bound to may close while the thread is still running. So we allocate + * something discrete for the task private data that can be definitively owned + * and freed by the threadpool, not the wsi... the pss won't do, as it only + * exists for the lifecycle of the wsi connection. + * + * When the task is created, we also tell it how to destroy the private data + * by giving it args.cleanup as cleanup_task_private_data() defined below. + */ + +static struct task_data * +create_task_private_data(void) +{ + struct task_data *priv = malloc(sizeof(*priv)); + + return priv; +} + +/* + * Destroy the private data for the task + * + * Notice the wsi the task was originally bound to may be long gone, in the + * case we are destroying the lws context and the thread was doing something + * for a long time without checking in. + */ +static void +cleanup_task_private_data(struct lws *wsi, void *user) +{ + struct task_data *priv = (struct task_data *)user; + + free(priv); +} + +/* + * This runs in its own thread, from the threadpool. + * + * The implementation behind this in lws uses pthreads, but no pthreadisms are + * required in the user code. + * + * The example counts to 10M, "checking in" to see if it should stop after every + * 100K and pausing to sync with the service thread to send a ws message every + * 1M. It resumes after the service thread determines the wsi is writable and + * the LWS_CALLBACK_SERVER_WRITEABLE indicates the task thread can continue by + * calling lws_threadpool_task_sync(). + */ + +static enum lws_threadpool_task_return +task_function(void *user, enum lws_threadpool_task_status s) +{ + struct task_data *priv = (struct task_data *)user; + int budget = 100 * 1000; + + if (priv->pos == priv->end) + return LWS_TP_RETURN_FINISHED; + + /* + * Preferably replace this with ~100ms of your real task, so it + * can "check in" at short intervals to see if it has been asked to + * stop. + * + * You can just run tasks atomically here with the thread dedicated + * to it, but it will cause odd delays while shutting down etc and + * the task will run to completion even if the wsi that started it + * has since closed. + */ + + while (budget--) + priv->pos++; + + usleep(100000); + + if (!(priv->pos % (1000 * 1000))) { + lws_snprintf(priv->result + LWS_PRE, + sizeof(priv->result) - LWS_PRE, + "pos %llu", (unsigned long long)priv->pos); + + return LWS_TP_RETURN_SYNC; + } + + return LWS_TP_RETURN_CHECKING_IN; +} + +static int +callback_minimal(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct per_vhost_data__minimal *vhd = + (struct per_vhost_data__minimal *) + lws_protocol_vh_priv_get(lws_get_vhost(wsi), + lws_get_protocol(wsi)); + const struct lws_protocol_vhost_options *pvo; + struct lws_threadpool_create_args cargs; + struct lws_threadpool_task_args args; + struct lws_threadpool_task *task; + struct task_data *priv; + int n, m, r = 0; + char name[32]; + void *_user; + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + /* create our per-vhost struct */ + vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), + lws_get_protocol(wsi), + sizeof(struct per_vhost_data__minimal)); + if (!vhd) + return 1; + + /* recover the pointer to the globals struct */ + pvo = lws_pvo_search( + (const struct lws_protocol_vhost_options *)in, + "config"); + if (!pvo || !pvo->value) { + lwsl_err("%s: Can't find \"config\" pvo\n", __func__); + return 1; + } + vhd->config = pvo->value; + + memset(&cargs, 0, sizeof(cargs)); + + cargs.max_queue_depth = 8; + cargs.threads = 3; + vhd->tp = lws_threadpool_create(lws_get_context(wsi), + &cargs, "%s", + lws_get_vhost_name(lws_get_vhost(wsi))); + if (!vhd->tp) + return 1; + + lws_timed_callback_vh_protocol(lws_get_vhost(wsi), + lws_get_protocol(wsi), + LWS_CALLBACK_USER, 1); + + break; + + case LWS_CALLBACK_PROTOCOL_DESTROY: + lws_threadpool_finish(vhd->tp); + lws_threadpool_destroy(vhd->tp); + break; + + case LWS_CALLBACK_USER: + + /* + * in debug mode, dump the threadpool stat to the logs once + * a second + */ + lws_threadpool_dump(vhd->tp); + lws_timed_callback_vh_protocol(lws_get_vhost(wsi), + lws_get_protocol(wsi), + LWS_CALLBACK_USER, 1); + break; + + case LWS_CALLBACK_ESTABLISHED: + + memset(&args, 0, sizeof(args)); + priv = args.user = create_task_private_data(); + if (!args.user) + return 1; + + priv->pos = 0; + priv->end = 10 * 1000 * 1000; + + /* queue the task... the task takes on responsibility for + * destroying args.user. pss->priv just has a copy of it */ + + args.wsi = wsi; + args.task = task_function; + args.cleanup = cleanup_task_private_data; + + lws_get_peer_simple(wsi, name, sizeof(name)); + + if (!lws_threadpool_enqueue(vhd->tp, &args, "ws %s", name)) { + lwsl_user("%s: Couldn't enqueue task\n", __func__); + cleanup_task_private_data(wsi, priv); + return 1; + } + + lws_set_timeout(wsi, PENDING_TIMEOUT_THREADPOOL, 30); + + /* + * so the asynchronous worker will let us know the next step + * by causing LWS_CALLBACK_SERVER_WRITEABLE + */ + + break; + + case LWS_CALLBACK_CLOSED: + break; + + case LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL: + lwsl_debug("LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL: %p\n", wsi); + lws_threadpool_dequeue(wsi); + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + + /* + * even completed tasks wait in a queue until we call the + * below on them. Then they may destroy themselves and their + * args.user data (by calling the cleanup callback). + * + * If you need to get things from the still-valid private task + * data, copy it here before calling + * lws_threadpool_task_status() that may free the task and the + * private task data. + */ + + n = lws_threadpool_task_status_wsi(wsi, &task, &_user); + lwsl_debug("%s: LWS_CALLBACK_SERVER_WRITEABLE: status %d\n", + __func__, n); + switch(n) { + + case LWS_TP_STATUS_FINISHED: + case LWS_TP_STATUS_STOPPED: + case LWS_TP_STATUS_QUEUED: + case LWS_TP_STATUS_RUNNING: + case LWS_TP_STATUS_STOPPING: + return 0; + + case LWS_TP_STATUS_SYNCING: + /* the task has paused for us to do something */ + break; + default: + return -1; + } + + priv = (struct task_data *)_user; + + lws_set_timeout(wsi, PENDING_TIMEOUT_THREADPOOL_TASK, 5); + + n = strlen(priv->result + LWS_PRE); + m = lws_write(wsi, (unsigned char *)priv->result + LWS_PRE, + n, LWS_WRITE_TEXT); + if (m < n) { + lwsl_err("ERROR %d writing to ws socket\n", m); + lws_threadpool_task_sync(task, 1); + return -1; + } + + /* + * service thread has done whatever it wanted to do with the + * data the task produced: if it's waiting to do more it can + * continue now. + */ + lws_threadpool_task_sync(task, 0); + break; + + default: + break; + } + + return r; +} + +#define LWS_PLUGIN_PROTOCOL_MINIMAL \ + { \ + "lws-minimal", \ + callback_minimal, \ + 0, \ + 128, \ + 0, NULL, 0 \ + } + +#if !defined (LWS_PLUGIN_STATIC) + +/* boilerplate needed if we are built as a dynamic plugin */ + +static const struct lws_protocols protocols[] = { + LWS_PLUGIN_PROTOCOL_MINIMAL +}; + +LWS_EXTERN LWS_VISIBLE int +init_protocol_minimal(struct lws_context *context, + struct lws_plugin_capability *c) +{ + if (c->api_magic != LWS_PLUGIN_API_MAGIC) { + lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC, + c->api_magic); + return 1; + } + + c->protocols = protocols; + c->count_protocols = LWS_ARRAY_SIZE(protocols); + c->extensions = NULL; + c->count_extensions = 0; + + return 0; +} + +LWS_EXTERN LWS_VISIBLE int +destroy_protocol_minimal(struct lws_context *context) +{ + return 0; +} +#endif