SL-20546: Merge branch 'DRTVWR-588-maint-W' into sl-20546.

master
Nat Goodspeed 2024-01-18 13:15:32 -05:00
commit fea1d9f4d2
1446 changed files with 52905 additions and 51704 deletions

36
.github/release.yaml vendored
View File

@ -1,18 +1,18 @@
changelog:
exclude:
labels:
- ignore-for-release
authors:
- dependabot
categories:
- title: Breaking Changes 🛠
labels:
- semver-major
- breaking-change
- title: New Features 🎉
labels:
- semver-minor
- enhancement
- title: Other Changes
labels:
- '*'
changelog:
exclude:
labels:
- ignore-for-release
authors:
- dependabot
categories:
- title: Breaking Changes 🛠
labels:
- semver-major
- breaking-change
- title: New Features 🎉
labels:
- semver-minor
- enhancement
- title: Other Changes
labels:
- '*'

View File

@ -261,7 +261,7 @@ jobs:
runs-on: windows
steps:
- name: Sign and package Windows viewer
uses: secondlife/viewer-build-util/sign-pkg-windows@main
uses: secondlife/viewer-build-util/sign-pkg-windows@v1
with:
vault_uri: "${{ secrets.AZURE_KEY_VAULT_URI }}"
cert_name: "${{ secrets.AZURE_CERT_NAME }}"
@ -293,7 +293,7 @@ jobs:
[[ -n "$USERNAME" && -n "$PASSWORD" && -n "$TEAM_ID" ]]
- name: Sign and package Mac viewer
uses: secondlife/viewer-build-util/sign-pkg-mac@main
uses: secondlife/viewer-build-util/sign-pkg-mac@v1
with:
channel: ${{ needs.build.outputs.viewer_channel }}
imagename: ${{ needs.build.outputs.imagename }}
@ -309,7 +309,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Post Windows symbols
uses: secondlife/viewer-build-util/post-bugsplat-windows@main
uses: secondlife/viewer-build-util/post-bugsplat-windows@v1
with:
username: ${{ secrets.BUGSPLAT_USER }}
password: ${{ secrets.BUGSPLAT_PASS }}
@ -322,7 +322,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Post Mac symbols
uses: secondlife/viewer-build-util/post-bugsplat-mac@main
uses: secondlife/viewer-build-util/post-bugsplat-mac@v1
with:
username: ${{ secrets.BUGSPLAT_USER }}
password: ${{ secrets.BUGSPLAT_PASS }}
@ -337,29 +337,29 @@ jobs:
steps:
- uses: actions/download-artifact@v3
with:
path: artifacts
name: Windows-installer
- name: Reshuffle artifact files
uses: secondlife/viewer-build-util/release-artifacts@main
- uses: actions/download-artifact@v3
with:
input-path: artifacts
output-path: assets
# The *-app artifacts are for use only by the signing and
# packaging steps. Once we've generated signed installers, we no
# longer need them, and we CERTAINLY don't want to publish
# thousands of individual files as separate URLs.
exclude: |-
Windows-app
macOS-app
# Use just "Windows" or "macOS" prefix because these are the only
# artifacts in which we expect files from both platforms with
# colliding names (e.g. autobuild-package.xml). release-artifacts
# normally resolves collisions by prepending the artifact name, so
# when we anticipate collisions, it's good to keep the prefix
# short and sweet.
prefix: |-
Windows-metadata=Windows
macOS-metadata=macOS
name: macOS-installer
- uses: actions/download-artifact@v3
with:
name: Windows-metadata
- name: Rename windows metadata
run: |
mv autobuild-package.xml Windows-autobuild-package.xml
mv newview/viewer_version.txt Windows-viewer_version.txt
- uses: actions/download-artifact@v3
with:
name: macOS-metadata
- name: Rename macOS metadata
run: |
mv autobuild-package.xml macOS-autobuild-package.xml
mv newview/viewer_version.txt macOS-viewer_version.txt
# forked from softprops/action-gh-release
- uses: secondlife-3p/action-gh-release@v1
@ -378,4 +378,8 @@ jobs:
append_body: true
# the only reason we generate a GH release is to post build products
fail_on_unmatched_files: true
files: "assets/*"
files: |
*.dmg
*.exe
*-autobuild-package.xml
*-viewer_version.txt

14
.gitignore vendored
View File

@ -7,9 +7,18 @@
*.pyc
*.rej
*.swp
*.vcxproj
*.filters
*.sln
*.depend
*.stamp
*.rc
*~
# Specific paths and/or names
CMakeCache.txt
cmake_install.cmake
LICENSES
build-darwin-*
build-linux-*
@ -17,6 +26,10 @@ debian/files
debian/secondlife-appearance-utility*
debian/secondlife-viewer*
indra/.distcc
indra/cmake/*
indra/out/*
indra/packages/*
build-vc80/
build-vc100/
build-vc120/
@ -76,3 +89,4 @@ web/locale.*
web/secondlife.com.*
.env
.vscode

View File

@ -702,11 +702,11 @@
<key>archive</key>
<map>
<key>hash</key>
<string>34af0a90a3015b7e7ec2486090bc4ce6ee5be758</string>
<string>7cc58b3acb230a7e65ea5f0ff800be393eb4aa1b</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
<string>https://github.com/secondlife/3p-glext/releases/download/v68-af397ee/glext-68-common-af397ee.tar.zst</string>
<string>https://github.com/secondlife/3p-glext/releases/download/v69/glext-68-common-685b36e.tar.zst</string>
</map>
<key>name</key>
<string>common</string>
@ -1468,11 +1468,11 @@
<key>archive</key>
<map>
<key>hash</key>
<string>e50ea94bbaa4ff41bf53b84b7192df1a694c5337</string>
<string>3a3e14563cd5fc019c3f139b82aa46ec79847709</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
<string>https://github.com/secondlife/llca/releases/download/v202310121525.0-d22bd98/llca-202310121530.0-common-d22bd98.tar.zst</string>
<string>https://github.com/secondlife/llca/releases/download/v202312051403.17-0f5d9c3/llca-202312051404.0-common-0f5d9c3.tar.zst</string>
</map>
<key>name</key>
<string>common</string>
@ -1486,7 +1486,7 @@
<string>Copyright (c) 2016, Linden Research, Inc.; data provided by the Mozilla NSS Project.
</string>
<key>version</key>
<string>202310121530.0</string>
<string>202312051404.0</string>
<key>name</key>
<string>llca</string>
</map>
@ -1732,6 +1732,62 @@
<key>description</key>
<string>Meshoptimizer. Mesh optimization library.</string>
</map>
<key>mikktspace</key>
<map>
<key>canonical_repo</key>
<string>https://bitbucket.org/lindenlab/3p-mikktspace</string>
<key>copyright</key>
<string>Copyright (C) 2011 by Morten S. Mikkelsen</string>
<key>description</key>
<string>Mikktspace Tangent Generator</string>
<key>license</key>
<string>Copyright (C) 2011 by Morten S. Mikkelsen</string>
<key>license_file</key>
<string>mikktspace.txt</string>
<key>name</key>
<string>mikktspace</string>
<key>platforms</key>
<map>
<key>darwin64</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>b48b7ac0792d3ea8f087d99d9e4a29d8</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/104415/914944/mikktspace-1-darwin64-574859.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
</map>
<key>windows</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>0a016b9c0c1e2c0b557e0124094da6c5</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/104407/914918/mikktspace-1-windows-574859.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
</map>
<key>windows64</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>02e9e5b6fe6788f4d2babb83ec544843</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/104406/914909/mikktspace-1-windows64-574859.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>1</string>
</map>
<key>minizip-ng</key>
<map>
<key>platforms</key>
@ -2318,6 +2374,42 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>name</key>
<string>threejs</string>
</map>
<key>tinygltf</key>
<map>
<key>canonical_repo</key>
<string>https://bitbucket.org/lindenlab/3p-tinygltf</string>
<key>copyright</key>
<string>// Copyright (c) 2015 - Present Syoyo Fujita, Aurélien Chatelain and many contributors.</string>
<key>description</key>
<string>tinygltf import library</string>
<key>license</key>
<string>MIT</string>
<key>license_file</key>
<string>LICENSES/tinygltf_license.txt</string>
<key>name</key>
<string>tinygltf</string>
<key>platforms</key>
<map>
<key>common</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>4dad1c0948141e1667c01a3ee755e4dc</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/105849/926137/tinygltf-v2.5.0-common-575729.tar.bz2</string>
</map>
<key>name</key>
<string>common</string>
</map>
</map>
<key>source</key>
<string>https://bitbucket.org/lindenlab/3p-tinygltf</string>
<key>source_type</key>
<string>git</string>
<key>version</key>
<string>v2.5.0</string>
</map>
<key>tracy</key>
<map>
<key>platforms</key>
@ -2327,11 +2419,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
<string>b84ccb1606b3fc5b216d0123a23a4922e02b6bd8</string>
<key>hash_algorithm</key>
<string>sha1</string>
<string>9b6e1a1f4b0969d38a1ca8ee00aeb548</string>
<key>url</key>
<string>https://github.com/secondlife/3p-tracy/releases/download/v0.8.1-235e98f/tracy-v0.8.1.235e98f-darwin64-235e98f.tar.zst</string>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/110584/960613/tracy-v0.8.1.578241-darwin64-578241.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
@ -2341,11 +2431,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
<string>54f126b85f179362cf0b6024e3cd621b53d68703</string>
<string>05b72ae5d733aed7d3bf142287601cc6</string>
<key>hash_algorithm</key>
<string>sha1</string>
<string>md5</string>
<key>url</key>
<string>https://github.com/secondlife/3p-tracy/releases/download/v0.8.1-235e98f/tracy-v0.8.1.235e98f-windows64-235e98f.tar.zst</string>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/110586/960637/tracy-v0.8.1.578241-windows64-578241.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
@ -2369,6 +2459,8 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>https://bitbucket.org/lindenlab/3p-tracy</string>
<key>source_type</key>
<string>git</string>
<key>version</key>
<string>v0.8.1.578241</string>
</map>
<key>tut</key>
<map>
@ -2471,11 +2563,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
<string>d8bc8720846cfa31e23e7e1008e32ba6ad4a8322</string>
<string>eb1316584188dafb591f80b46b357c737f90d1a7</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0.cc7ea1e/viewer_manager-3.0.cc7ea1e-darwin64-cc7ea1e.tar.zst</string>
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0-08bf5ee/viewer_manager-3.0-08bf5ee-darwin64-08bf5ee.tar.zst</string>
</map>
<key>name</key>
<string>darwin64</string>
@ -2485,11 +2577,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
<string>228fae4ee0ce12b9d1d1b0a8ebb0bdf58ee521eb</string>
<string>f4677b0ebd9880f29c118af51ada50883dd0a1e4</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0.cc7ea1e/viewer_manager-3.0.cc7ea1e-linux64-cc7ea1e.tar.zst</string>
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0-08bf5ee/viewer_manager-3.0-08bf5ee-linux64-08bf5ee.tar.zst</string>
</map>
<key>name</key>
<string>linux64</string>
@ -2499,11 +2591,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
<string>ca6999b64d96d45952fe872b381db9b2abc0248c</string>
<string>7426c5a1d7eb231b476625637a1f2daba0a6bc55</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0.cc7ea1e/viewer_manager-3.0.cc7ea1e-windows64-cc7ea1e.tar.zst</string>
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0-08bf5ee/viewer_manager-3.0-08bf5ee-windows64-08bf5ee.tar.zst</string>
</map>
<key>name</key>
<string>windows64</string>
@ -2516,7 +2608,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>copyright</key>
<string>Copyright (c) 2000-2012, Linden Research, Inc.</string>
<key>version</key>
<string>3.0.cc7ea1e</string>
<string>3.0-08bf5ee</string>
<key>name</key>
<string>viewer-manager</string>
<key>description</key>
@ -2630,6 +2722,62 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>description</key>
<string>XMLRPC Library</string>
</map>
<key>vulkan_gltf</key>
<map>
<key>canonical_repo</key>
<string>https://bitbucket.org/lindenlab/3p-vulkan-gltf-pbr</string>
<key>copyright</key>
<string>Copyright (c) 2018 Sascha Willems</string>
<key>description</key>
<string>Vulkan GLTF Sample Implementation</string>
<key>license</key>
<string>Copyright (c) 2018 Sascha Willems</string>
<key>license_file</key>
<string>LICENSES/vulkan_gltf.txt</string>
<key>name</key>
<string>vulkan_gltf</string>
<key>platforms</key>
<map>
<key>darwin64</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>8cff2060843db3db788511ee34a8e8cc</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/101316/891509/vulkan_gltf-1-darwin64-572743.tar.bz2</string>
</map>
<key>name</key>
<string>darwin64</string>
</map>
<key>windows</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>58eea384be49ba756ce9c5e66669540b</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/101318/891520/vulkan_gltf-1-windows-572743.tar.bz2</string>
</map>
<key>name</key>
<string>windows</string>
</map>
<key>windows64</key>
<map>
<key>archive</key>
<map>
<key>hash</key>
<string>79b6a11622c2f83cfc2b7cd1fafb867b</string>
<key>url</key>
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/101319/891521/vulkan_gltf-1-windows64-572743.tar.bz2</string>
</map>
<key>name</key>
<string>windows64</string>
</map>
</map>
<key>version</key>
<string>1</string>
</map>
<key>xxhash</key>
<map>
<key>platforms</key>
@ -2785,7 +2933,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>-DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE</string>
<string>-DROOT_PROJECT_NAME:STRING=SecondLife</string>
<string>-DINSTALL_PROPRIETARY=TRUE</string>
</array>
</array>
</map>
<key>build</key>
<map>
@ -2805,11 +2953,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>-DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE</string>
<string>-DROOT_PROJECT_NAME:STRING=SecondLife</string>
<string>-DINSTALL_PROPRIETARY=FALSE</string>
</array>
</array>
<key>arguments</key>
<array>
<string>../indra</string>
</array>
</array>
</map>
<key>name</key>
<string>RelWithDebInfoOS</string>
@ -2826,7 +2974,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>-DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE</string>
<string>-DROOT_PROJECT_NAME:STRING=SecondLife</string>
<string>-DINSTALL_PROPRIETARY=TRUE</string>
</array>
</array>
</map>
<key>build</key>
<map>
@ -2846,11 +2994,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>-DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE</string>
<string>-DROOT_PROJECT_NAME:STRING=SecondLife</string>
<string>-DINSTALL_PROPRIETARY=FALSE</string>
</array>
</array>
<key>arguments</key>
<array>
<string>../indra</string>
</array>
</array>
</map>
<key>name</key>
<string>ReleaseOS</string>
@ -2871,11 +3019,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<array>
<string>-G</string>
<string>Xcode</string>
</array>
</array>
<key>arguments</key>
<array>
<string>../indra</string>
</array>
</array>
</map>
<key>build</key>
<map>
@ -2887,7 +3035,8 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>RelWithDebInfo</string>
<string>-project</string>
<string>SecondLife.xcodeproj</string>
</array>
<string>-parallelizeTargets</string>
</array>
</map>
<key>default</key>
<string>True</string>
@ -2902,7 +3051,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<array>
<string>-G</string>
<string>Xcode</string>
</array>
</array>
</map>
<key>build</key>
<map>
@ -2914,7 +3063,8 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>RelWithDebInfo</string>
<string>-project</string>
<string>SecondLife.xcodeproj</string>
</array>
<string>-parallelizeTargets</string>
</array>
</map>
<key>name</key>
<string>RelWithDebInfoOS</string>
@ -2927,11 +3077,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<array>
<string>-G</string>
<string>Xcode</string>
</array>
</array>
<key>arguments</key>
<array>
<string>../indra</string>
</array>
</array>
</map>
<key>build</key>
<map>
@ -2943,7 +3093,8 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>Release</string>
<string>-project</string>
<string>SecondLife.xcodeproj</string>
</array>
<string>-parallelizeTargets</string>
</array>
</map>
<key>name</key>
<string>Release</string>
@ -2956,7 +3107,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<array>
<string>-G</string>
<string>Xcode</string>
</array>
</array>
</map>
<key>build</key>
<map>
@ -2968,7 +3119,8 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>Release</string>
<string>-project</string>
<string>SecondLife.xcodeproj</string>
</array>
<string>-parallelizeTargets</string>
</array>
</map>
<key>name</key>
<string>ReleaseOS</string>
@ -2992,11 +3144,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>-G</string>
<string>Ninja</string>
<string>-DLL_TESTS=Off</string>
</array>
</array>
<key>arguments</key>
<array>
<string>../indra</string>
</array>
</array>
</map>
<key>build</key>
<map>
@ -3017,7 +3169,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>-G</string>
<string>Ninja</string>
<string>-DLL_TESTS=Off</string>
</array>
</array>
</map>
<key>build</key>
<map>
@ -3055,11 +3207,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>${AUTOBUILD_WIN_CMAKE_GEN|NOTWIN}</string>
<string>-A</string>
<string>${AUTOBUILD_WIN_VSPLATFORM|NOTWIN}</string>
</array>
</array>
<key>arguments</key>
<array>
<string>..\indra</string>
</array>
</array>
</map>
<key>build</key>
<map>
@ -3069,11 +3221,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<array>
<string>/build</string>
<string>RelWithDebInfo|${AUTOBUILD_WIN_VSPLATFORM|NOTWIN}</string>
</array>
</array>
<key>arguments</key>
<array>
<string>SecondLife.sln</string>
</array>
</array>
</map>
<key>default</key>
<string>True</string>
@ -3093,11 +3245,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>-DINSTALL_PROPRIETARY=FALSE</string>
<string>-DUSE_KDU=FALSE</string>
<string>-DUSE_OPENAL:BOOL=ON</string>
</array>
</array>
<key>arguments</key>
<array>
<string>..\indra</string>
</array>
</array>
</map>
<key>build</key>
<map>
@ -3111,11 +3263,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>/p:useenv=true</string>
<string>/verbosity:minimal</string>
<string>/p:VCBuildAdditionalOptions= /incremental</string>
</array>
</array>
<key>arguments</key>
<array>
<string>SecondLife.sln</string>
</array>
</array>
</map>
<key>name</key>
<string>RelWithDebInfoOS</string>
@ -3130,11 +3282,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>${AUTOBUILD_WIN_CMAKE_GEN|NOTWIN}</string>
<string>-A</string>
<string>${AUTOBUILD_WIN_VSPLATFORM|NOTWIN}</string>
</array>
</array>
<key>arguments</key>
<array>
<string>..\indra</string>
</array>
</array>
</map>
<key>build</key>
<map>
@ -3144,11 +3296,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<array>
<string>/build</string>
<string>Release|${AUTOBUILD_WIN_VSPLATFORM|NOTWIN}</string>
</array>
</array>
<key>arguments</key>
<array>
<string>SecondLife.sln</string>
</array>
</array>
</map>
<key>name</key>
<string>Release</string>
@ -3167,11 +3319,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>-DINSTALL_PROPRIETARY=FALSE</string>
<string>-DUSE_KDU=FALSE</string>
<string>-DUSE_OPENAL:BOOL=ON</string>
</array>
</array>
<key>arguments</key>
<array>
<string>..\indra</string>
</array>
</array>
</map>
<key>build</key>
<map>
@ -3185,11 +3337,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>/p:useenv=true</string>
<string>/verbosity:minimal</string>
<string>/p:VCBuildAdditionalOptions= /incremental</string>
</array>
</array>
<key>arguments</key>
<array>
<string>SecondLife.sln</string>
</array>
</array>
</map>
<key>name</key>
<string>ReleaseOS</string>
@ -3217,4 +3369,4 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>Second Life Viewer</string>
</map>
</map>
</llsd>
</llsd>

View File

@ -19,6 +19,7 @@ Agathos Frascati
CT-317
CT-352
Ai Austin
SL-19399
Aiko Ying
Aimee Trescothick
SNOW-227
@ -239,6 +240,7 @@ Ansariel Hiller
SL-18432
SL-19140
SL-4126
SL-20224
Aralara Rajal
Arare Chantilly
CHUIBUG-191
@ -290,6 +292,7 @@ Beq Janus
SL-18637
SL-19317
SL-19660
SL-20610
Beth Walcher
Bezilon Kasei
Biancaluce Robbiani
@ -595,6 +598,7 @@ Henri Beauchamp
SL-15175
SL-19110
SL-19159
[NO JIRA] (fullbright HUD alpha fix)
herina Bode
Hikkoshi Sakai
VWR-429
@ -927,6 +931,8 @@ LSL Scientist
Lamorna Proctor
Lares Carter
Larry Pixel
Lars Næsbye Christensen
SL-20054
Laurent Bechir
Leal Choche
Lenae Munz
@ -1418,6 +1424,7 @@ Sovereign Engineer
SL-18497
SL-18525
SL-18534
SL-19690
SL-19336
SpacedOut Frye
VWR-34

View File

@ -29,15 +29,6 @@ else()
set( USE_AUTOBUILD_3P ON )
endif()
# The viewer code base can now be successfully compiled with -std=c++14. But
# turning that on in the generic viewer-build-variables/variables file would
# potentially require tweaking each of our ~50 third-party library builds.
# Until we decide to set -std=c++14 in viewer-build-variables/variables, set
# it locally here: we want to at least prevent inadvertently reintroducing
# viewer code that would fail with C++14.
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(Variables)
include(BuildVersion)

View File

@ -187,3 +187,4 @@ if (LINUX OR DARWIN)
endif (LINUX OR DARWIN)

View File

@ -64,6 +64,7 @@ set(cmake_SOURCE_FILES
VisualLeakDetector.cmake
LibVLCPlugin.cmake
XmlRpcEpi.cmake
xxHash.cmake
ZLIBNG.cmake
)

View File

@ -86,7 +86,8 @@ if(WINDOWS)
endif (USE_BUGSPLAT)
if (TARGET ll::fmodstudio)
set(debug_files ${debug_files} fmodL.dll)
# fmodL is included for logging, only one should be picked by manifest
set(release_files ${release_files} fmodL.dll)
set(release_files ${release_files} fmod.dll)
endif ()

View File

@ -2,7 +2,7 @@
include_guard()
# FMODSTUDIO can be set when launching the make using the argument -DFMODSTUDIO:BOOL=ON
# FMODSTUDIO can be set when launching the make using the argument -DUSE_FMODSTUDIO:BOOL=ON
# When building using proprietary binaries though (i.e. having access to LL private servers),
# we always build with FMODSTUDIO.
if (INSTALL_PROPRIETARY)

View File

@ -3,9 +3,7 @@ include(Prebuilt)
include(GLH)
add_library( ll::glext INTERFACE IMPORTED )
if (WINDOWS OR LINUX)
use_system_binary(glext)
use_prebuilt_binary(glext)
endif (WINDOWS OR LINUX)
use_system_binary(glext)
use_prebuilt_binary(glext)

View File

@ -126,6 +126,13 @@ MACRO(LL_ADD_PROJECT_UNIT_TESTS project sources)
message("LL_ADD_PROJECT_UNIT_TESTS ${name}_test_additional_CFLAGS ${${name}_test_additional_CFLAGS}")
endif()
if (DARWIN)
# test binaries always need to be signed for local development
set_target_properties(PROJECT_${project}_TEST_${name}
PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-")
endif ()
#
# Setup test targets
#
@ -221,6 +228,13 @@ FUNCTION(LL_ADD_INTEGRATION_TEST
)
endif ()
if (DARWIN)
# test binaries always need to be signed for local development
set_target_properties(INTEGRATION_TEST_${testname}
PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-")
endif ()
# Add link deps to the executable
if(TEST_DEBUG)
message(STATUS "TARGET_LINK_LIBRARIES(INTEGRATION_TEST_${testname} ${libraries})")

View File

@ -1,2 +1,5 @@
# -*- cmake -*-
include(Variables)
include(Mikktspace)

View File

@ -0,0 +1,6 @@
# -*- cmake -*-
include(Prebuilt)
if (NOT USESYSTEMLIBS)
use_prebuilt_binary(mikktspace)
endif (NOT USESYSTEMLIBS)

View File

@ -13,7 +13,7 @@ elseif (WINDOWS)
foreach(hive HKEY_CURRENT_USER HKEY_LOCAL_MACHINE)
# prefer more recent Python versions to older ones, if multiple versions
# are installed
foreach(pyver 3.11 3.10 3.9 3.8 3.7)
foreach(pyver 3.12 3.11 3.10 3.9 3.8 3.7)
list(APPEND regpaths "[${hive}\\SOFTWARE\\Python\\PythonCore\\${pyver}\\InstallPath]")
endforeach()
endforeach()
@ -40,7 +40,7 @@ elseif (WINDOWS)
${regpaths}
${pymaybe}
)
include(FindPythonInterp)
find_package(Python3 COMPONENTS Interpreter)
else()
find_program(python python3)

View File

@ -0,0 +1,7 @@
# -*- cmake -*-
include(Prebuilt)
use_prebuilt_binary(tinygltf)
set(TINYGLTF_INCLUDE_DIR ${LIBS_PREBUILT_DIR}/include/tinygltf)

View File

@ -11,8 +11,9 @@ if (USE_TRACY)
use_prebuilt_binary(tracy)
target_include_directories( ll::tracy SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/include/tracy)
target_link_libraries( ll::tracy INTERFACE TracyClient )
# See: indra/llcommon/llprofiler.h
target_compile_definitions(ll::tracy INTERFACE LL_PROFILER_CONFIGURATION=3 )
# See: indra/llcommon/llprofiler.h
add_compile_definitions(LL_PROFILER_CONFIGURATION=3)
endif (USE_TRACY)

View File

@ -33,7 +33,7 @@ set(LIBS_OPEN_PREFIX)
set(SCRIPTS_PREFIX ../scripts)
set(VIEWER_PREFIX)
set(INTEGRATION_TESTS_PREFIX)
set(LL_TESTS ON CACHE BOOL "Build and run unit and integration tests (disable for build timing runs to reduce variation")
set(LL_TESTS OFF CACHE BOOL "Build and run unit and integration tests (disable for build timing runs to reduce variation")
set(INCREMENTAL_LINK OFF CACHE BOOL "Use incremental linking on win32 builds (enable for faster links on some machines)")
set(ENABLE_MEDIA_PLUGINS ON CACHE BOOL "Turn off building media plugins if they are imported by third-party library mechanism")
set(VIEWER_SYMBOL_FILE "" CACHE STRING "Name of tarball into which to place symbol files")

View File

@ -0,0 +1,5 @@
# -*- cmake -*-
include(Prebuilt)
use_prebuilt_binary(vulkan_gltf)

View File

@ -1050,7 +1050,6 @@ BOOL LLAvatarAppearance::loadSkeletonNode ()
mRoot->addChild(mMeshLOD[MESH_ID_UPPER_BODY]);
mRoot->addChild(mMeshLOD[MESH_ID_LOWER_BODY]);
mRoot->addChild(mMeshLOD[MESH_ID_SKIRT]);
mRoot->addChild(mMeshLOD[MESH_ID_HEAD]);
LLAvatarJoint *skull = (LLAvatarJoint*)mRoot->findJoint("mSkull");
if (skull)

View File

@ -377,7 +377,6 @@ BOOL LLTexLayerSet::render( S32 x, S32 y, S32 width, S32 height, LLRenderTarget*
// clear buffer area to ensure we don't pick up UI elements
{
gGL.flush();
LLGLDisable no_alpha(GL_ALPHA_TEST);
gAlphaMaskProgram.setMinimumAlpha(0.0f);
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
gGL.color4f( 0.f, 0.f, 0.f, 1.f );
@ -410,7 +409,6 @@ BOOL LLTexLayerSet::render( S32 x, S32 y, S32 width, S32 height, LLRenderTarget*
gGL.flush();
gGL.setSceneBlendType(LLRender::BT_REPLACE);
LLGLDisable no_alpha(GL_ALPHA_TEST);
gAlphaMaskProgram.setMinimumAlpha(0.f);
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
@ -500,7 +498,6 @@ void LLTexLayerSet::renderAlphaMaskTextures(S32 x, S32 y, S32 width, S32 height,
{
// Set the alpha channel to one (clean up after previous blending)
gGL.flush();
LLGLDisable no_alpha(GL_ALPHA_TEST);
gAlphaMaskProgram.setMinimumAlpha(0.f);
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
gGL.color4f( 0.f, 0.f, 0.f, 1.f );
@ -1025,7 +1022,6 @@ void LLTexLayer::calculateTexLayerColor(const param_color_list_t &param_list, LL
BOOL LLTexLayer::render(S32 x, S32 y, S32 width, S32 height, LLRenderTarget* bound_target)
{
LLGLEnable color_mat(GL_COLOR_MATERIAL);
// *TODO: Is this correct?
//gPipeline.disableLights();
stop_glerror();
@ -1112,7 +1108,6 @@ BOOL LLTexLayer::render(S32 x, S32 y, S32 width, S32 height, LLRenderTarget* bou
if( tex )
{
bool no_alpha_test = getInfo()->mWriteAllChannels;
LLGLDisable alpha_test(no_alpha_test ? GL_ALPHA_TEST : 0);
if (no_alpha_test)
{
gAlphaMaskProgram.setMinimumAlpha(0.f);
@ -1162,7 +1157,6 @@ BOOL LLTexLayer::render(S32 x, S32 y, S32 width, S32 height, LLRenderTarget* bou
getInfo()->mStaticImageFileName.empty() &&
color_specified )
{
LLGLDisable no_alpha(GL_ALPHA_TEST);
gAlphaMaskProgram.setMinimumAlpha(0.000f);
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
@ -1260,7 +1254,6 @@ BOOL LLTexLayer::blendAlphaTexture(S32 x, S32 y, S32 width, S32 height)
LLGLTexture* tex = LLTexLayerStaticImageList::getInstance()->getTexture( getInfo()->mStaticImageFileName, getInfo()->mStaticImageIsMask );
if( tex )
{
LLGLSNoAlphaTest gls_no_alpha_test;
gAlphaMaskProgram.setMinimumAlpha(0.f);
gGL.getTexUnit(0)->bind(tex, TRUE);
gl_rect_2d_simple_tex( width, height );
@ -1279,7 +1272,6 @@ BOOL LLTexLayer::blendAlphaTexture(S32 x, S32 y, S32 width, S32 height)
LLGLTexture* tex = mLocalTextureObject->getImage();
if (tex)
{
LLGLSNoAlphaTest gls_no_alpha_test;
gAlphaMaskProgram.setMinimumAlpha(0.f);
gGL.getTexUnit(0)->bind(tex);
gl_rect_2d_simple_tex( width, height );
@ -1316,7 +1308,6 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
// Note: if the first param is a mulitply, multiply against the current buffer's alpha
if( !first_param || !first_param->getMultiplyBlend() )
{
LLGLDisable no_alpha(GL_ALPHA_TEST);
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
// Clear the alpha
@ -1328,7 +1319,6 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
}
// Accumulate alphas
LLGLSNoAlphaTest gls_no_alpha_test;
gGL.color4f( 1.f, 1.f, 1.f, 1.f );
for (LLTexLayerParamAlpha* param : mParamAlphaList)
{
@ -1350,7 +1340,6 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
LLGLTexture* tex = mLocalTextureObject->getImage();
if( tex && (tex->getComponents() == 4) )
{
LLGLSNoAlphaTest gls_no_alpha_test;
LLTexUnit::eTextureAddressMode old_mode = tex->getAddressMode();
gGL.getTexUnit(0)->bind(tex, TRUE);
@ -1370,7 +1359,6 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
{
if( (tex->getComponents() == 4) || (tex->getComponents() == 1) )
{
LLGLSNoAlphaTest gls_no_alpha_test;
gGL.getTexUnit(0)->bind(tex, TRUE);
gl_rect_2d_simple_tex( width, height );
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
@ -1387,7 +1375,6 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
// Note: we're still using gGL.blendFunc( GL_DST_ALPHA, GL_ZERO );
if ( !is_approx_equal(layer_color.mV[VW], 1.f) )
{
LLGLDisable no_alpha(GL_ALPHA_TEST);
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
gGL.color4fv(layer_color.mV);
gl_rect_2d_simple( width, height );
@ -1472,7 +1459,14 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
}
else
{ // platforms with working drivers...
glReadPixels(x, y, width, height, GL_ALPHA, GL_UNSIGNED_BYTE, alpha_data);
// We just want GL_ALPHA, but that isn't supported in OGL core profile 4.
static const size_t TEMP_BYTES_PER_PIXEL = 4;
U8* temp_data = (U8*)ll_aligned_malloc_32(mem_size * TEMP_BYTES_PER_PIXEL);
glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, temp_data);
for (size_t pixel = 0; pixel < pixels; pixel++) {
alpha_data[pixel] = temp_data[(pixel * TEMP_BYTES_PER_PIXEL) + 3];
}
ll_aligned_free_32(temp_data);
}
}
else

View File

@ -149,7 +149,7 @@ LLTexLayerParamAlpha::LLTexLayerParamAlpha(const LLTexLayerParamAlpha& pOther)
mCachedProcessedTexture(pOther.mCachedProcessedTexture),
mStaticImageTGA(pOther.mStaticImageTGA),
mStaticImageRaw(pOther.mStaticImageRaw),
mNeedsCreateTexture(pOther.mNeedsCreateTexture),
mNeedsCreateTexture(pOther.mNeedsCreateTexture.load()),
mStaticImageInvalid(pOther.mStaticImageInvalid),
mAvgDistortionVec(pOther.mAvgDistortionVec),
mCachedEffectiveWeight(pOther.mCachedEffectiveWeight)
@ -344,7 +344,6 @@ BOOL LLTexLayerParamAlpha::render(S32 x, S32 y, S32 width, S32 height)
mCachedProcessedTexture->setAddressMode(LLTexUnit::TAM_CLAMP);
}
LLGLSNoAlphaTest gls_no_alpha_test;
gGL.getTexUnit(0)->bind(mCachedProcessedTexture);
gl_rect_2d_simple_tex(width, height);
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
@ -361,7 +360,6 @@ BOOL LLTexLayerParamAlpha::render(S32 x, S32 y, S32 width, S32 height)
}
else
{
LLGLDisable no_alpha(GL_ALPHA_TEST);
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
gGL.color4f(0.f, 0.f, 0.f, effective_weight);
gl_rect_2d_simple(width, height);

View File

@ -100,7 +100,7 @@ private:
LLPointer<LLGLTexture> mCachedProcessedTexture;
LLPointer<LLImageTGA> mStaticImageTGA;
LLPointer<LLImageRaw> mStaticImageRaw;
BOOL mNeedsCreateTexture;
std::atomic<BOOL> mNeedsCreateTexture;
BOOL mStaticImageInvalid;
LL_ALIGN_16(LLVector4a mAvgDistortionVec);
F32 mCachedEffectiveWeight;

View File

@ -607,40 +607,37 @@ void LLAudioDecodeMgr::Impl::startMoreDecodes()
// Kick off a decode
mDecodes[decode_id] = LLPointer<LLVorbisDecodeState>(NULL);
try
{
main_queue->postTo(
general_queue,
[decode_id]() // Work done on general queue
bool posted = main_queue->postTo(
general_queue,
[decode_id]() // Work done on general queue
{
LLPointer<LLVorbisDecodeState> decode_state = beginDecodingAndWritingAudio(decode_id);
if (!decode_state)
{
LLPointer<LLVorbisDecodeState> decode_state = beginDecodingAndWritingAudio(decode_id);
if (!decode_state)
{
// Audio decode has errored
return decode_state;
}
// Disk write of decoded audio is now in progress off-thread
// Audio decode has errored
return decode_state;
},
[decode_id, this](LLPointer<LLVorbisDecodeState> decode_state) // Callback to main thread
mutable {
if (!gAudiop)
{
// There is no LLAudioEngine anymore. This might happen if
// an audio decode is enqueued just before shutdown.
return;
}
}
// At this point, we can be certain that the pointer to "this"
// is valid because the lifetime of "this" is dependent upon
// the lifetime of gAudiop.
// Disk write of decoded audio is now in progress off-thread
return decode_state;
},
[decode_id, this](LLPointer<LLVorbisDecodeState> decode_state) // Callback to main thread
mutable {
if (!gAudiop)
{
// There is no LLAudioEngine anymore. This might happen if
// an audio decode is enqueued just before shutdown.
return;
}
enqueueFinishAudio(decode_id, decode_state);
});
}
catch (const LLThreadSafeQueueInterrupt&)
// At this point, we can be certain that the pointer to "this"
// is valid because the lifetime of "this" is dependent upon
// the lifetime of gAudiop.
enqueueFinishAudio(decode_id, decode_state);
});
if (! posted)
{
// Shutdown
// Consider making processQueue() do a cleanup instead

View File

@ -16,7 +16,10 @@ include(Tracy)
set(llcommon_SOURCE_FILES
apply.cpp
commoncontrol.cpp
indra_constants.cpp
lazyeventapi.cpp
llallocator.cpp
llallocator_heap_profile.cpp
llapp.cpp
@ -114,11 +117,16 @@ set(llcommon_SOURCE_FILES
set(llcommon_HEADER_FILES
CMakeLists.txt
always_return.h
apply.h
chrono.h
classic_callback.h
commoncontrol.h
ctype_workaround.h
fix_macros.h
function_types.h
indra_constants.h
lazyeventapi.h
linden_common.h
llalignedarray.h
llallocator.h
@ -172,6 +180,7 @@ set(llcommon_HEADER_FILES
llinitdestroyclass.h
llinitparam.h
llinstancetracker.h
llinstancetrackersubclass.h
llkeybind.h
llkeythrottle.h
llleap.h
@ -245,6 +254,7 @@ set(llcommon_HEADER_FILES
stdtypes.h
stringize.h
threadpool.h
threadpool_fwd.h
threadsafeschedule.h
timer.h
tuple.h
@ -288,9 +298,11 @@ if (LL_TESTS)
#set(TEST_DEBUG on)
set(test_libs llcommon)
LL_ADD_INTEGRATION_TEST(apply "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(classic_callback "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llcond "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}")

View File

@ -0,0 +1,124 @@
/**
* @file always_return.h
* @author Nat Goodspeed
* @date 2023-01-20
* @brief Call specified callable with arbitrary arguments, but always return
* specified type.
*
* $LicenseInfo:firstyear=2023&license=viewerlgpl$
* Copyright (c) 2023, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_ALWAYS_RETURN_H)
#define LL_ALWAYS_RETURN_H
#include <type_traits> // std::enable_if, std::is_convertible
namespace LL
{
#if __cpp_lib_is_invocable >= 201703L // C++17
template <typename CALLABLE, typename... ARGS>
using invoke_result = std::invoke_result<CALLABLE, ARGS...>;
#else // C++14
template <typename CALLABLE, typename... ARGS>
using invoke_result = std::result_of<CALLABLE(ARGS...)>;
#endif // C++14
/**
* AlwaysReturn<T>()(some_function, some_args...) calls
* some_function(some_args...). It is guaranteed to return a value of type
* T, regardless of the return type of some_function(). If some_function()
* returns a type convertible to T, it will convert and return that value.
* Otherwise (notably if some_function() is void), AlwaysReturn returns
* T().
*
* When some_function() returns a type not convertible to T, if
* you want AlwaysReturn to return some T value other than
* default-constructed T(), pass that value to AlwaysReturn's constructor.
*/
template <typename DESIRED>
class AlwaysReturn
{
public:
/// pass explicit default value if other than default-constructed type
AlwaysReturn(const DESIRED& dft=DESIRED()): mDefault(dft) {}
// callable returns a type not convertible to DESIRED, return default
template <typename CALLABLE, typename... ARGS,
typename std::enable_if<
! std::is_convertible<
typename invoke_result<CALLABLE, ARGS...>::type,
DESIRED
>::value,
bool
>::type=true>
DESIRED operator()(CALLABLE&& callable, ARGS&&... args)
{
// discard whatever callable(args) returns
std::forward<CALLABLE>(callable)(std::forward<ARGS>(args)...);
return mDefault;
}
// callable returns a type convertible to DESIRED
template <typename CALLABLE, typename... ARGS,
typename std::enable_if<
std::is_convertible<
typename invoke_result<CALLABLE, ARGS...>::type,
DESIRED
>::value,
bool
>::type=true>
DESIRED operator()(CALLABLE&& callable, ARGS&&... args)
{
return { std::forward<CALLABLE>(callable)(std::forward<ARGS>(args)...) };
}
private:
DESIRED mDefault;
};
/**
* always_return<T>(some_function, some_args...) calls
* some_function(some_args...). It is guaranteed to return a value of type
* T, regardless of the return type of some_function(). If some_function()
* returns a type convertible to T, it will convert and return that value.
* Otherwise (notably if some_function() is void), always_return() returns
* T().
*/
template <typename DESIRED, typename CALLABLE, typename... ARGS>
DESIRED always_return(CALLABLE&& callable, ARGS&&... args)
{
return AlwaysReturn<DESIRED>()(std::forward<CALLABLE>(callable),
std::forward<ARGS>(args)...);
}
/**
* make_always_return<T>(some_function) returns a callable which, when
* called with appropriate some_function() arguments, always returns a
* value of type T, regardless of the return type of some_function(). If
* some_function() returns a type convertible to T, the returned callable
* will convert and return that value. Otherwise (notably if
* some_function() is void), the returned callable returns T().
*
* When some_function() returns a type not convertible to T, if
* you want the returned callable to return some T value other than
* default-constructed T(), pass that value to make_always_return() as its
* optional second argument.
*/
template <typename DESIRED, typename CALLABLE>
auto make_always_return(CALLABLE&& callable, const DESIRED& dft=DESIRED())
{
return
[dft, callable = std::forward<CALLABLE>(callable)]
(auto&&... args)
{
return AlwaysReturn<DESIRED>(dft)(callable,
std::forward<decltype(args)>(args)...);
};
}
} // namespace LL
#endif /* ! defined(LL_ALWAYS_RETURN_H) */

29
indra/llcommon/apply.cpp Normal file
View File

@ -0,0 +1,29 @@
/**
* @file apply.cpp
* @author Nat Goodspeed
* @date 2022-12-21
* @brief Implementation for apply.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
// Precompiled header
#include "linden_common.h"
// associated header
#include "apply.h"
// STL headers
// std headers
// external library headers
// other Linden headers
#include "stringize.h"
void LL::apply_validate_size(size_t size, size_t arity)
{
if (size != arity)
{
LLTHROW(apply_error(stringize("LL::apply(func(", arity, " args), "
"std::vector(", size, " elements))")));
}
}

View File

@ -12,8 +12,11 @@
#if ! defined(LL_APPLY_H)
#define LL_APPLY_H
#include "llexception.h"
#include <boost/type_traits/function_traits.hpp>
#include <functional> // std::mem_fn()
#include <tuple>
#include <type_traits> // std::is_member_pointer
namespace LL
{
@ -54,20 +57,67 @@ namespace LL
}, \
(ARGS))
#if __cplusplus >= 201703L
/*****************************************************************************
* invoke()
*****************************************************************************/
#if __cpp_lib_invoke >= 201411L
// C++17 implementation
using std::apply;
using std::invoke;
#else // no std::invoke
// Use invoke() to handle pointer-to-method:
// derived from https://stackoverflow.com/a/38288251
template<typename Fn, typename... Args,
typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value,
int>::type = 0 >
auto invoke(Fn&& f, Args&&... args)
{
return std::mem_fn(std::forward<Fn>(f))(std::forward<Args>(args)...);
}
template<typename Fn, typename... Args,
typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::type>::value,
int>::type = 0 >
auto invoke(Fn&& f, Args&&... args)
{
return std::forward<Fn>(f)(std::forward<Args>(args)...);
}
#endif // no std::invoke
/*****************************************************************************
* apply(function, tuple); apply(function, array)
*****************************************************************************/
#if __cpp_lib_apply >= 201603L
// C++17 implementation
// We don't just say 'using std::apply;' because that template is too general:
// it also picks up the apply(function, vector) case, which we want to handle
// below.
template <typename CALLABLE, typename... ARGS>
auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
{
return std::apply(std::forward<CALLABLE>(func), args);
}
#else // C++14
// Derived from https://stackoverflow.com/a/20441189
// and https://en.cppreference.com/w/cpp/utility/apply
template <typename CALLABLE, typename TUPLE, std::size_t... I>
auto apply_impl(CALLABLE&& func, TUPLE&& args, std::index_sequence<I...>)
template <typename CALLABLE, typename... ARGS, std::size_t... I>
auto apply_impl(CALLABLE&& func, const std::tuple<ARGS...>& args, std::index_sequence<I...>)
{
// We accept const std::tuple& so a caller can construct an tuple on the
// fly. But std::get<I>(const tuple) adds a const qualifier to everything
// it extracts. Get a non-const ref to this tuple so we can extract
// without the extraneous const.
auto& non_const_args{ const_cast<std::tuple<ARGS...>&>(args) };
// call func(unpacked args)
return std::forward<CALLABLE>(func)(std::move(std::get<I>(args))...);
return invoke(std::forward<CALLABLE>(func),
std::forward<ARGS>(std::get<I>(non_const_args))...);
}
template <typename CALLABLE, typename... ARGS>
@ -81,6 +131,8 @@ auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
std::index_sequence_for<ARGS...>{});
}
#endif // C++14
// per https://stackoverflow.com/a/57510428/5533635
template <typename CALLABLE, typename T, size_t SIZE>
auto apply(CALLABLE&& func, const std::array<T, SIZE>& args)
@ -88,28 +140,92 @@ auto apply(CALLABLE&& func, const std::array<T, SIZE>& args)
return apply(std::forward<CALLABLE>(func), std::tuple_cat(args));
}
/*****************************************************************************
* bind_front()
*****************************************************************************/
// To invoke a non-static member function with a tuple, you need a callable
// that binds your member function with an instance pointer or reference.
// std::bind_front() is perfect: std::bind_front(&cls::method, instance).
// Unfortunately bind_front() only enters the standard library in C++20.
#if __cpp_lib_bind_front >= 201907L
// C++20 implementation
using std::bind_front;
#else // no std::bind_front()
template<typename Fn, typename... Args,
typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::type>::value,
int>::type = 0 >
auto bind_front(Fn&& f, Args&&... args)
{
// Don't use perfect forwarding for f or args: we must bind them for later.
return [f, pfx_args=std::make_tuple(args...)]
(auto&&... sfx_args)
{
// Use perfect forwarding for sfx_args because we use them as soon as
// we receive them.
return apply(
f,
std::tuple_cat(pfx_args,
std::make_tuple(std::forward<decltype(sfx_args)>(sfx_args)...)));
};
}
template<typename Fn, typename... Args,
typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value,
int>::type = 0 >
auto bind_front(Fn&& f, Args&&... args)
{
return bind_front(std::mem_fn(std::forward<Fn>(f)), std::forward<Args>(args)...);
}
#endif // C++20 with std::bind_front()
/*****************************************************************************
* apply(function, std::vector)
*****************************************************************************/
// per https://stackoverflow.com/a/28411055/5533635
template <typename CALLABLE, typename T, std::size_t... I>
auto apply_impl(CALLABLE&& func, const std::vector<T>& args, std::index_sequence<I...>)
{
return apply_impl(std::forward<CALLABLE>(func),
std::make_tuple(std::forward<T>(args[I])...),
I...);
return apply(std::forward<CALLABLE>(func),
std::make_tuple(args[I]...));
}
// this goes beyond C++17 std::apply()
// produce suitable error if apply(func, vector) is the wrong size for func()
void apply_validate_size(size_t size, size_t arity);
/// possible exception from apply() validation
struct apply_error: public LLException
{
apply_error(const std::string& what): LLException(what) {}
};
template <size_t ARITY, typename CALLABLE, typename T>
auto apply_n(CALLABLE&& func, const std::vector<T>& args)
{
apply_validate_size(args.size(), ARITY);
return apply_impl(std::forward<CALLABLE>(func),
args,
std::make_index_sequence<ARITY>());
}
/**
* apply(function, std::vector) goes beyond C++17 std::apply(). For this case
* @a function @emph cannot be variadic: the compiler must know at compile
* time how many arguments to pass. This isn't Python. (But see apply_n() to
* pass a specific number of args to a variadic function.)
*/
template <typename CALLABLE, typename T>
auto apply(CALLABLE&& func, const std::vector<T>& args)
{
// infer arity from the definition of func
constexpr auto arity = boost::function_traits<CALLABLE>::arity;
assert(args.size() == arity);
return apply_impl(std::forward<CALLABLE>(func),
args,
std::make_index_sequence<arity>());
// now that we have a compile-time arity, apply_n() works
return apply_n<arity>(std::forward<CALLABLE>(func), args);
}
#endif // C++14
} // namespace LL
#endif /* ! defined(LL_APPLY_H) */

View File

@ -0,0 +1,106 @@
/**
* @file commoncontrol.cpp
* @author Nat Goodspeed
* @date 2022-06-08
* @brief Implementation for commoncontrol.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
// Precompiled header
#include "linden_common.h"
// associated header
#include "commoncontrol.h"
// STL headers
// std headers
// external library headers
// other Linden headers
#include "llevents.h"
#include "llsdutil.h"
LLSD LL::CommonControl::access(const LLSD& params)
{
// We can't actually introduce a link-time dependency on llxml, or on any
// global LLControlGroup (*koff* gSavedSettings *koff*) but we can issue a
// runtime query. If we're running as part of a viewer with
// LLViewerControlListener, we can use that to interact with any
// instantiated LLControGroup.
LLSD response;
{
LLEventStream reply("reply");
LLTempBoundListener connection = reply.listen("listener",
[&response] (const LLSD& event)
{
response = event;
return false;
});
LLSD rparams{ params };
rparams["reply"] = reply.getName();
LLEventPumps::instance().obtain("LLViewerControl").post(rparams);
}
// LLViewerControlListener responds immediately. If it's listening at all,
// it will already have set response.
if (! response.isDefined())
{
LLTHROW(NoListener("No LLViewerControl listener instantiated"));
}
LLSD error{ response["error"] };
if (error.isDefined())
{
LLTHROW(ParamError(error));
}
response.erase("error");
response.erase("reqid");
return response;
}
/// set control group.key to defined default value
LLSD LL::CommonControl::set_default(const std::string& group, const std::string& key)
{
return access(llsd::map("op", "set",
"group", group, "key", key))["value"];
}
/// set control group.key to specified value
LLSD LL::CommonControl::set(const std::string& group, const std::string& key, const LLSD& value)
{
return access(llsd::map("op", "set",
"group", group, "key", key, "value", value))["value"];
}
/// toggle boolean control group.key
LLSD LL::CommonControl::toggle(const std::string& group, const std::string& key)
{
return access(llsd::map("op", "toggle",
"group", group, "key", key))["value"];
}
/// get the definition for control group.key, (! isDefined()) if bad
/// ["name"], ["type"], ["value"], ["comment"]
LLSD LL::CommonControl::get_def(const std::string& group, const std::string& key)
{
return access(llsd::map("op", "get",
"group", group, "key", key));
}
/// get the value of control group.key
LLSD LL::CommonControl::get(const std::string& group, const std::string& key)
{
return access(llsd::map("op", "get",
"group", group, "key", key))["value"];
}
/// get defined groups
std::vector<std::string> LL::CommonControl::get_groups()
{
auto groups{ access(llsd::map("op", "groups"))["groups"] };
return { groups.beginArray(), groups.endArray() };
}
/// get definitions for all variables in group
LLSD LL::CommonControl::get_vars(const std::string& group)
{
return access(llsd::map("op", "vars", "group", group))["vars"];
}

View File

@ -0,0 +1,75 @@
/**
* @file commoncontrol.h
* @author Nat Goodspeed
* @date 2022-06-08
* @brief Access LLViewerControl LLEventAPI, if process has one.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_COMMONCONTROL_H)
#define LL_COMMONCONTROL_H
#include <vector>
#include "llexception.h"
#include "llsd.h"
namespace LL
{
class CommonControl
{
public:
struct Error: public LLException
{
Error(const std::string& what): LLException(what) {}
};
/// Exception thrown if there's no LLViewerControl LLEventAPI
struct NoListener: public Error
{
NoListener(const std::string& what): Error(what) {}
};
struct ParamError: public Error
{
ParamError(const std::string& what): Error(what) {}
};
/// set control group.key to defined default value
static
LLSD set_default(const std::string& group, const std::string& key);
/// set control group.key to specified value
static
LLSD set(const std::string& group, const std::string& key, const LLSD& value);
/// toggle boolean control group.key
static
LLSD toggle(const std::string& group, const std::string& key);
/// get the definition for control group.key, (! isDefined()) if bad
/// ["name"], ["type"], ["value"], ["comment"]
static
LLSD get_def(const std::string& group, const std::string& key);
/// get the value of control group.key
static
LLSD get(const std::string& group, const std::string& key);
/// get defined groups
static
std::vector<std::string> get_groups();
/// get definitions for all variables in group
static
LLSD get_vars(const std::string& group);
private:
static
LLSD access(const LLSD& params);
};
} // namespace LL
#endif /* ! defined(LL_COMMONCONTROL_H) */

View File

@ -0,0 +1,49 @@
/**
* @file function_types.h
* @author Nat Goodspeed
* @date 2023-01-20
* @brief Extend boost::function_types to examine boost::function and
* std::function
*
* $LicenseInfo:firstyear=2023&license=viewerlgpl$
* Copyright (c) 2023, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_FUNCTION_TYPES_H)
#define LL_FUNCTION_TYPES_H
#include <boost/function.hpp>
#include <boost/function_types/function_arity.hpp>
#include <functional>
namespace LL
{
template <typename F>
struct function_arity_impl
{
static constexpr auto value = boost::function_types::function_arity<F>::value;
};
template <typename F>
struct function_arity_impl<std::function<F>>
{
static constexpr auto value = function_arity_impl<F>::value;
};
template <typename F>
struct function_arity_impl<boost::function<F>>
{
static constexpr auto value = function_arity_impl<F>::value;
};
template <typename F>
struct function_arity
{
static constexpr auto value = function_arity_impl<typename std::decay<F>::type>::value;
};
} // namespace LL
#endif /* ! defined(LL_FUNCTION_TYPES_H) */

View File

@ -0,0 +1,72 @@
/**
* @file lazyeventapi.cpp
* @author Nat Goodspeed
* @date 2022-06-17
* @brief Implementation for lazyeventapi.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
// Precompiled header
#include "linden_common.h"
// associated header
#include "lazyeventapi.h"
// STL headers
// std headers
#include <algorithm> // std::find_if
// external library headers
// other Linden headers
#include "llevents.h"
#include "llsdutil.h"
LL::LazyEventAPIBase::LazyEventAPIBase(
const std::string& name, const std::string& desc, const std::string& field)
{
// populate embedded LazyEventAPIParams instance
mParams.name = name;
mParams.desc = desc;
mParams.field = field;
// mParams.init and mOperations are populated by subsequent add() calls.
// Our raison d'etre: register as an LLEventPumps::PumpFactory
// so obtain() will notice any request for this name and call us.
// Of course, our subclass constructor must finish running (making add()
// calls) before mParams will be fully populated, but we expect that to
// happen well before the first LLEventPumps::obtain(name) call.
mRegistered = LLEventPumps::instance().registerPumpFactory(
name,
[this](const std::string& name){ return construct(name); });
}
LL::LazyEventAPIBase::~LazyEventAPIBase()
{
// If our constructor's registerPumpFactory() call was unsuccessful, that
// probably means somebody else claimed the name first. If that's the
// case, do NOT unregister their name out from under them!
// If this is a static instance being destroyed at process shutdown,
// LLEventPumps will probably have been cleaned up already.
if (mRegistered && ! LLEventPumps::wasDeleted())
{
// unregister the callback to this doomed instance
LLEventPumps::instance().unregisterPumpFactory(mParams.name);
}
}
LLSD LL::LazyEventAPIBase::getMetadata(const std::string& name) const
{
// Since mOperations is a vector rather than a map, just search.
auto found = std::find_if(mOperations.begin(), mOperations.end(),
[&name](const auto& namedesc)
{ return (namedesc.first == name); });
if (found == mOperations.end())
return {};
// LLEventDispatcher() supplements the returned metadata in different
// ways, depending on metadata provided to the specific add() method.
// Don't try to emulate all that. At some point we might consider more
// closely unifying LLEventDispatcher machinery with LazyEventAPI, but for
// now this will have to do.
return llsd::map("name", found->first, "desc", found->second);
}

View File

@ -0,0 +1,205 @@
/**
* @file lazyeventapi.h
* @author Nat Goodspeed
* @date 2022-06-16
* @brief Declaring a static module-scope LazyEventAPI registers a specific
* LLEventAPI for future on-demand instantiation.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_LAZYEVENTAPI_H)
#define LL_LAZYEVENTAPI_H
#include "apply.h"
#include "lleventapi.h"
#include "llinstancetracker.h"
#include <boost/signals2/signal.hpp>
#include <string>
#include <tuple>
#include <utility> // std::pair
#include <vector>
namespace LL
{
/**
* Bundle params we want to pass to LLEventAPI's protected constructor. We
* package them this way so a subclass constructor can simply forward an
* opaque reference to the LLEventAPI constructor.
*/
// This is a class instead of a plain struct mostly so when we forward-
// declare it we don't have to remember the distinction.
class LazyEventAPIParams
{
public:
// package the parameters used by the normal LLEventAPI constructor
std::string name, desc, field;
// bundle LLEventAPI::add() calls collected by LazyEventAPI::add(), so
// the special LLEventAPI constructor we engage can "play back" those
// add() calls
boost::signals2::signal<void(LLEventAPI*)> init;
};
/**
* LazyEventAPIBase implements most of the functionality of LazyEventAPI
* (q.v.), but we need the LazyEventAPI template subclass so we can accept
* the specific LLEventAPI subclass type.
*/
// No LLInstanceTracker key: we don't need to find a specific instance,
// LLLeapListener just needs to be able to enumerate all instances.
class LazyEventAPIBase: public LLInstanceTracker<LazyEventAPIBase>
{
public:
LazyEventAPIBase(const std::string& name, const std::string& desc,
const std::string& field);
virtual ~LazyEventAPIBase();
// Do not copy or move: once constructed, LazyEventAPIBase must stay
// put: we bind its instance pointer into a callback.
LazyEventAPIBase(const LazyEventAPIBase&) = delete;
LazyEventAPIBase(LazyEventAPIBase&&) = delete;
LazyEventAPIBase& operator=(const LazyEventAPIBase&) = delete;
LazyEventAPIBase& operator=(LazyEventAPIBase&&) = delete;
// capture add() calls we want to play back on LLEventAPI construction
template <typename... ARGS>
void add(const std::string& name, const std::string& desc, ARGS&&... rest)
{
// capture the metadata separately
mOperations.push_back(std::make_pair(name, desc));
// Use connect_extended() so the lambda is passed its own
// connection.
// apply() can't accept a template per se; it needs a particular
// specialization. Specialize out here to work around a clang bug:
// https://github.com/llvm/llvm-project/issues/41999
auto func{ &LazyEventAPIBase::add_trampoline
<const std::string&, const std::string&, ARGS...> };
// We can't bind an unexpanded parameter pack into a lambda --
// shame really. Instead, capture all our args as a std::tuple and
// then, in the lambda, use apply() to pass to add_trampoline().
auto args{ std::make_tuple(name, desc, std::forward<ARGS>(rest)...) };
mParams.init.connect_extended(
[func, args]
(const boost::signals2::connection& conn, LLEventAPI* instance)
{
// we only need this connection once
conn.disconnect();
// apply() expects a tuple specifying ALL the arguments,
// so prepend instance.
apply(func, std::tuple_cat(std::make_tuple(instance), args));
});
}
// The following queries mimic the LLEventAPI / LLEventDispatcher
// query API.
// Get the string name of the subject LLEventAPI
std::string getName() const { return mParams.name; }
// Get the documentation string
std::string getDesc() const { return mParams.desc; }
// Retrieve the LLSD key we use for dispatching
std::string getDispatchKey() const { return mParams.field; }
// operations
using NameDesc = std::pair<std::string, std::string>;
private:
// metadata that might be queried by LLLeapListener
std::vector<NameDesc> mOperations;
public:
using const_iterator = decltype(mOperations)::const_iterator;
const_iterator begin() const { return mOperations.begin(); }
const_iterator end() const { return mOperations.end(); }
LLSD getMetadata(const std::string& name) const;
protected:
// Params with which to instantiate the companion LLEventAPI subclass
LazyEventAPIParams mParams;
private:
// true if we successfully registered our LLEventAPI on construction
bool mRegistered;
// actually instantiate the companion LLEventAPI subclass
virtual LLEventPump* construct(const std::string& name) = 0;
// Passing an overloaded function to any function that accepts an
// arbitrary callable is a PITB because you have to specify the
// correct overload. What we want is for the compiler to select the
// correct overload, based on the carefully-wrought enable_ifs in
// LLEventDispatcher. This (one and only) add_trampoline() method
// exists solely to pass to LL::apply(). Once add_trampoline() is
// called with the expanded arguments, we hope the compiler will Do
// The Right Thing in selecting the correct LLEventAPI::add()
// overload.
template <typename... ARGS>
static
void add_trampoline(LLEventAPI* instance, ARGS&&... args)
{
instance->add(std::forward<ARGS>(args)...);
}
};
/**
* LazyEventAPI provides a way to register a particular LLEventAPI to be
* instantiated on demand, that is, when its name is passed to
* LLEventPumps::obtain().
*
* Derive your listener from LLEventAPI as usual, with its various
* operation methods, but code your constructor to accept
* <tt>(const LL::LazyEventAPIParams& params)</tt>
* and forward that reference to (the protected)
* <tt>LLEventAPI(const LL::LazyEventAPIParams&)</tt> constructor.
*
* Then derive your listener registrar from
* <tt>LazyEventAPI<your LLEventAPI subclass></tt>. The constructor should
* look very like a traditional LLEventAPI constructor:
*
* * pass (name, desc [, field]) to LazyEventAPI's constructor
* * in the body, make a series of add() calls referencing your LLEventAPI
* subclass methods.
*
* You may use any LLEventAPI::add() methods, that is, any
* LLEventDispatcher::add() methods. But the target methods you pass to
* add() must belong to your LLEventAPI subclass, not the LazyEventAPI
* subclass.
*
* Declare a static instance of your LazyEventAPI listener registrar
* class. When it's constructed at static initialization time, it will
* register your LLEventAPI subclass with LLEventPumps. It will also
* collect metadata for the LLEventAPI and its operations to provide to
* LLLeapListener's introspection queries.
*
* When someone later calls LLEventPumps::obtain() to post an event to
* your LLEventAPI subclass, obtain() will instantiate it using
* LazyEventAPI's name, desc, field and add() calls.
*/
template <class EVENTAPI>
class LazyEventAPI: public LazyEventAPIBase
{
public:
// for subclass constructor to reference handler methods
using listener = EVENTAPI;
LazyEventAPI(const std::string& name, const std::string& desc,
const std::string& field="op"):
// Forward ctor params to LazyEventAPIBase
LazyEventAPIBase(name, desc, field)
{}
private:
LLEventPump* construct(const std::string& /*name*/) override
{
// base class has carefully assembled LazyEventAPIParams embedded
// in this instance, just pass to LLEventAPI subclass constructor
return new EVENTAPI(mParams);
}
};
} // namespace LL
#endif /* ! defined(LL_LAZYEVENTAPI_H) */

View File

@ -528,6 +528,7 @@ S32 LLAPRFile::seek(apr_file_t* file_handle, apr_seek_where_t where, S32 offset)
//static
S32 LLAPRFile::readEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool)
{
LL_PROFILE_ZONE_SCOPED;
//*****************************************
LLAPRFilePoolScope scope(pool);
apr_file_t* file_handle = open(filename, scope.getVolatileAPRPool(), APR_READ|APR_BINARY);
@ -572,6 +573,7 @@ S32 LLAPRFile::readEx(const std::string& filename, void *buf, S32 offset, S32 nb
//static
S32 LLAPRFile::writeEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool)
{
LL_PROFILE_ZONE_SCOPED;
apr_int32_t flags = APR_CREATE|APR_WRITE|APR_BINARY;
if (offset < 0)
{

View File

@ -96,6 +96,7 @@ LLAssetDictionary::LLAssetDictionary()
addEntry(LLAssetType::AT_WIDGET, new AssetEntry("WIDGET", "widget", "widget", false, false, false));
addEntry(LLAssetType::AT_PERSON, new AssetEntry("PERSON", "person", "person", false, false, false));
addEntry(LLAssetType::AT_SETTINGS, new AssetEntry("SETTINGS", "settings", "settings blob", true, true, true));
addEntry(LLAssetType::AT_MATERIAL, new AssetEntry("MATERIAL", "material", "render material", true, true, true));
addEntry(LLAssetType::AT_UNKNOWN, new AssetEntry("UNKNOWN", "invalid", NULL, false, false, false));
addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, FALSE, FALSE, FALSE));

View File

@ -127,8 +127,9 @@ public:
AT_RESERVED_6 = 55,
AT_SETTINGS = 56, // Collection of settings
AT_COUNT = 57,
AT_MATERIAL = 57, // Render Material
AT_COUNT = 58,
// +*********************************************************+
// | TO ADD AN ELEMENT TO THIS ENUM: |

View File

@ -79,9 +79,9 @@ struct LLContextStatus
LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLContextStatus& context_status);
#define dumpStack(tag) \
if (debugLoggingEnabled(tag)) \
{ \
LLCallStack cs; \
LL_DEBUGS(tag) << "STACK:\n" << "====================\n" << cs << "====================" << LL_ENDL; \
}
#define dumpStack(tag) \
LL_DEBUGS(tag) << "STACK:\n" \
<< "====================\n" \
<< LLCallStack() \
<< "====================" \
<< LL_ENDL;

View File

@ -37,12 +37,13 @@ thread_local bool gProfilerEnabled = false;
#if (TRACY_ENABLE)
// Override new/delete for tracy memory profiling
void *operator new(size_t size)
void* ll_tracy_new(size_t size)
{
void* ptr;
if (gProfilerEnabled)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
//LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
ptr = (malloc)(size);
}
else
@ -57,12 +58,22 @@ void *operator new(size_t size)
return ptr;
}
void operator delete(void *ptr) noexcept
void* operator new(size_t size)
{
return ll_tracy_new(size);
}
void* operator new[](std::size_t count)
{
return ll_tracy_new(count);
}
void ll_tracy_delete(void* ptr)
{
TracyFree(ptr);
if (gProfilerEnabled)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
//LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
(free)(ptr);
}
else
@ -71,6 +82,16 @@ void operator delete(void *ptr) noexcept
}
}
void operator delete(void *ptr) noexcept
{
ll_tracy_delete(ptr);
}
void operator delete[](void* ptr) noexcept
{
ll_tracy_delete(ptr);
}
// C-style malloc/free can't be so easily overridden, so we define tracy versions and use
// a pre-processor #define in linden_common.h to redirect to them. The parens around the native
// functions below prevents recursive substitution by the preprocessor.

View File

@ -278,6 +278,7 @@ std::string LLCoros::launch(const std::string& prefix, const callable_t& callabl
catch (std::bad_alloc&)
{
// Out of memory on stack allocation?
printActiveCoroutines();
LL_ERRS("LLCoros") << "Bad memory allocation in LLCoros::launch(" << prefix << ")!" << LL_ENDL;
}

View File

@ -1603,20 +1603,6 @@ namespace LLError
}
}
bool debugLoggingEnabled(const std::string& tag)
{
LLMutexTrylock lock(getMutex<LOG_MUTEX>(), 5);
if (!lock.isLocked())
{
return false;
}
SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
LLError::ELevel level = LLError::LEVEL_DEBUG;
bool res = checkLevelMap(s->mTagLevelMap, tag, level);
return res;
}
void crashdriver(void (*callback)(int*))
{
// The LLERROR_CRASH macro used to have inline code of the form:

View File

@ -82,9 +82,11 @@ const int LL_ERR_NOERR = 0;
#ifdef SHOW_ASSERT
#define llassert(func) llassert_always_msg(func, #func)
#define llassert_msg(func, msg) llassert_always_msg(func, msg)
#define llverify(func) llassert_always_msg(func, #func)
#else
#define llassert(func)
#define llassert_msg(func, msg)
#define llverify(func) do {if (func) {}} while(0)
#endif
@ -462,8 +464,31 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG;
LLError::CallSite& _site(_sites[which]); \
lllog_test_()
// Check at run-time whether logging is enabled, without generating output
/*
// Check at run-time whether logging is enabled, without generating output.
Resist the temptation to add a function like this because it incurs the
expense of locking and map-searching every time control reaches it.
bool debugLoggingEnabled(const std::string& tag);
Instead of:
if debugLoggingEnabled("SomeTag")
{
// ... presumably expensive operation ...
LL_DEBUGS("SomeTag") << ... << LL_ENDL;
}
Use this:
LL_DEBUGS("SomeTag");
// ... presumably expensive operation ...
LL_CONT << ...;
LL_ENDL;
LL_DEBUGS("SomeTag") performs the locking and map-searching ONCE, then caches
the result in a static variable.
*/
// used by LLERROR_CRASH
void crashdriver(void (*)(int*));

View File

@ -35,6 +35,7 @@
// external library headers
// other Linden headers
#include "llerror.h"
#include "lazyeventapi.h"
LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const std::string& field):
lbase(name, field),
@ -43,6 +44,13 @@ LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const s
{
}
LLEventAPI::LLEventAPI(const LL::LazyEventAPIParams& params):
LLEventAPI(params.name, params.desc, params.field)
{
// call initialization functions with our brand-new instance pointer
params.init(this);
}
LLEventAPI::~LLEventAPI()
{
}

View File

@ -35,6 +35,11 @@
#include "llinstancetracker.h"
#include <string>
namespace LL
{
class LazyEventAPIParams;
}
/**
* LLEventAPI not only provides operation dispatch functionality, inherited
* from LLDispatchListener -- it also gives us event API introspection.
@ -64,19 +69,6 @@ public:
/// Get the documentation string
std::string getDesc() const { return mDesc; }
/**
* Publish only selected add() methods from LLEventDispatcher.
* Every LLEventAPI add() @em must have a description string.
*/
template <typename CALLABLE>
void add(const std::string& name,
const std::string& desc,
CALLABLE callable,
const LLSD& required=LLSD())
{
LLEventDispatcher::add(name, desc, callable, required);
}
/**
* Instantiate a Response object in any LLEventAPI subclass method that
* wants to guarantee a reply (if requested) will be sent on exit from the
@ -150,16 +142,20 @@ public:
* @endcode
*/
LLSD& operator[](const LLSD::String& key) { return mResp[key]; }
/**
* set the response to the given data
*/
void setResponse(LLSD const & response){ mResp = response; }
/**
* set the response to the given data
*/
void setResponse(LLSD const & response){ mResp = response; }
LLSD mResp, mReq;
LLSD::String mKey;
};
protected:
// constructor used only by subclasses registered by LazyEventAPI
LLEventAPI(const LL::LazyEventAPIParams&);
private:
std::string mDesc;
};

View File

@ -40,70 +40,12 @@
// other Linden headers
#include "llevents.h"
#include "llerror.h"
#include "llexception.h"
#include "llsdutil.h"
#include "stringize.h"
#include <iomanip> // std::quoted()
#include <memory> // std::auto_ptr
/*****************************************************************************
* LLSDArgsSource
*****************************************************************************/
/**
* Store an LLSD array, producing its elements one at a time. Die with LL_ERRS
* if the consumer requests more elements than the array contains.
*/
class LL_COMMON_API LLSDArgsSource
{
public:
LLSDArgsSource(const std::string function, const LLSD& args);
~LLSDArgsSource();
LLSD next();
void done() const;
private:
std::string _function;
LLSD _args;
LLSD::Integer _index;
};
LLSDArgsSource::LLSDArgsSource(const std::string function, const LLSD& args):
_function(function),
_args(args),
_index(0)
{
if (! (_args.isUndefined() || _args.isArray()))
{
LL_ERRS("LLSDArgsSource") << _function << " needs an args array instead of "
<< _args << LL_ENDL;
}
}
LLSDArgsSource::~LLSDArgsSource()
{
done();
}
LLSD LLSDArgsSource::next()
{
if (_index >= _args.size())
{
LL_ERRS("LLSDArgsSource") << _function << " requires more arguments than the "
<< _args.size() << " provided: " << _args << LL_ENDL;
}
return _args[_index++];
}
void LLSDArgsSource::done() const
{
if (_index < _args.size())
{
LL_WARNS("LLSDArgsSource") << _function << " only consumed " << _index
<< " of the " << _args.size() << " arguments provided: "
<< _args << LL_ENDL;
}
}
/*****************************************************************************
* LLSDArgsMapper
*****************************************************************************/
@ -156,19 +98,26 @@ void LLSDArgsSource::done() const
* - Holes are filled with the default values.
* - Any remaining holes constitute an error.
*/
class LL_COMMON_API LLSDArgsMapper
class LL_COMMON_API LLEventDispatcher::LLSDArgsMapper
{
public:
/// Accept description of function: function name, param names, param
/// default values
LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults);
LLSDArgsMapper(LLEventDispatcher* parent, const std::string& function,
const LLSD& names, const LLSD& defaults);
/// Given arguments map, return LLSD::Array of parameter values, or LL_ERRS.
/// Given arguments map, return LLSD::Array of parameter values, or
/// trigger error.
LLSD map(const LLSD& argsmap) const;
private:
static std::string formatlist(const LLSD&);
template <typename... ARGS>
[[noreturn]] void callFail(ARGS&&... args) const;
// store a plain dumb back-pointer because we don't have to manage the
// parent LLEventDispatcher's lifespan
LLEventDispatcher* _parent;
// The function-name string is purely descriptive. We want error messages
// to be able to indicate which function's LLSDArgsMapper has the problem.
std::string _function;
@ -187,15 +136,18 @@ private:
FilledVector _has_dft;
};
LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
const LLSD& names, const LLSD& defaults):
LLEventDispatcher::LLSDArgsMapper::LLSDArgsMapper(LLEventDispatcher* parent,
const std::string& function,
const LLSD& names,
const LLSD& defaults):
_parent(parent),
_function(function),
_names(names),
_has_dft(names.size())
{
if (! (_names.isUndefined() || _names.isArray()))
{
LL_ERRS("LLSDArgsMapper") << function << " names must be an array, not " << names << LL_ENDL;
callFail(" names must be an array, not ", names);
}
auto nparams(_names.size());
// From _names generate _indexes.
@ -218,8 +170,7 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
// defaults is a (possibly empty) array. Right-align it with names.
if (ndefaults > nparams)
{
LL_ERRS("LLSDArgsMapper") << function << " names array " << names
<< " shorter than defaults array " << defaults << LL_ENDL;
callFail(" names array ", names, " shorter than defaults array ", defaults);
}
// Offset by which we slide defaults array right to right-align with
@ -256,23 +207,20 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
}
if (bogus.size())
{
LL_ERRS("LLSDArgsMapper") << function << " defaults specified for nonexistent params "
<< formatlist(bogus) << LL_ENDL;
callFail(" defaults specified for nonexistent params ", formatlist(bogus));
}
}
else
{
LL_ERRS("LLSDArgsMapper") << function << " defaults must be a map or an array, not "
<< defaults << LL_ENDL;
callFail(" defaults must be a map or an array, not ", defaults);
}
}
LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
LLSD LLEventDispatcher::LLSDArgsMapper::map(const LLSD& argsmap) const
{
if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray()))
{
LL_ERRS("LLSDArgsMapper") << _function << " map() needs a map or array, not "
<< argsmap << LL_ENDL;
callFail(" map() needs a map or array, not ", argsmap);
}
// Initialize the args array. Indexing a non-const LLSD array grows it
// to appropriate size, but we don't want to resize this one on each
@ -369,15 +317,14 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
// by argsmap, that's a problem.
if (unfilled.size())
{
LL_ERRS("LLSDArgsMapper") << _function << " missing required arguments "
<< formatlist(unfilled) << " from " << argsmap << LL_ENDL;
callFail(" missing required arguments ", formatlist(unfilled), " from ", argsmap);
}
// done
return args;
}
std::string LLSDArgsMapper::formatlist(const LLSD& list)
std::string LLEventDispatcher::LLSDArgsMapper::formatlist(const LLSD& list)
{
std::ostringstream out;
const char* delim = "";
@ -390,23 +337,44 @@ std::string LLSDArgsMapper::formatlist(const LLSD& list)
return out.str();
}
LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
mDesc(desc),
mKey(key)
template <typename... ARGS>
[[noreturn]] void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const
{
_parent->callFail<LLEventDispatcher::DispatchError>
(_function, std::forward<ARGS>(args)...);
}
/*****************************************************************************
* LLEventDispatcher
*****************************************************************************/
LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
LLEventDispatcher(desc, key, "args")
{}
LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key,
const std::string& argskey):
mDesc(desc),
mKey(key),
mArgskey(argskey)
{}
LLEventDispatcher::~LLEventDispatcher()
{
}
LLEventDispatcher::DispatchEntry::DispatchEntry(LLEventDispatcher* parent, const std::string& desc):
mParent(parent),
mDesc(desc)
{}
/**
* DispatchEntry subclass used for callables accepting(const LLSD&)
*/
struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchEntry
{
LLSDDispatchEntry(const std::string& desc, const Callable& func, const LLSD& required):
DispatchEntry(desc),
LLSDDispatchEntry(LLEventDispatcher* parent, const std::string& desc,
const Callable& func, const LLSD& required):
DispatchEntry(parent, desc),
mFunc(func),
mRequired(required)
{}
@ -414,22 +382,21 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE
Callable mFunc;
LLSD mRequired;
virtual void call(const std::string& desc, const LLSD& event) const
LLSD call(const std::string& desc, const LLSD& event, bool, const std::string&) const override
{
// Validate the syntax of the event itself.
std::string mismatch(llsd_matches(mRequired, event));
if (! mismatch.empty())
{
LL_ERRS("LLEventDispatcher") << desc << ": bad request: " << mismatch << LL_ENDL;
callFail(desc, ": bad request: ", mismatch);
}
// Event syntax looks good, go for it!
mFunc(event);
return mFunc(event);
}
virtual LLSD addMetadata(LLSD meta) const
LLSD getMetadata() const override
{
meta["required"] = mRequired;
return meta;
return llsd::map("required", mRequired);
}
};
@ -439,17 +406,27 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE
*/
struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry
{
ParamsDispatchEntry(const std::string& desc, const invoker_function& func):
DispatchEntry(desc),
ParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
const std::string& desc, const invoker_function& func):
DispatchEntry(parent, desc),
mName(name),
mInvoker(func)
{}
std::string mName;
invoker_function mInvoker;
virtual void call(const std::string& desc, const LLSD& event) const
LLSD call(const std::string&, const LLSD& event, bool, const std::string&) const override
{
LLSDArgsSource src(desc, event);
mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src)));
try
{
return mInvoker(event);
}
catch (const LL::apply_error& err)
{
// could hit runtime errors with LL::apply()
callFail(err.what());
}
}
};
@ -459,23 +436,62 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc
*/
struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
{
ArrayParamsDispatchEntry(const std::string& desc, const invoker_function& func,
ArrayParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
const std::string& desc, const invoker_function& func,
LLSD::Integer arity):
ParamsDispatchEntry(desc, func),
ParamsDispatchEntry(parent, name, desc, func),
mArity(arity)
{}
LLSD::Integer mArity;
virtual LLSD addMetadata(LLSD meta) const
LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override
{
// std::string context { stringize(desc, "(", event, ") with argskey ", std::quoted(argskey), ": ") };
// Whether we try to extract arguments from 'event' depends on whether
// the LLEventDispatcher consumer called one of the (name, event)
// methods (! fromMap) or one of the (event) methods (fromMap). If we
// were called with (name, event), the passed event must itself be
// suitable to pass to the registered callable, no args extraction
// required or even attempted. Only if called with plain (event) do we
// consider extracting args from that event. Initially assume 'event'
// itself contains the arguments.
LLSD args{ event };
if (fromMap)
{
if (! mArity)
{
// When the target function is nullary, and we're called from
// an (event) method, just ignore the rest of the map entries.
args.clear();
}
else
{
// We only require/retrieve argskey if the target function
// isn't nullary. For all others, since we require an LLSD
// array, we must have an argskey.
if (argskey.empty())
{
callFail("LLEventDispatcher has no args key");
}
if ((! event.has(argskey)))
{
callFail("missing required key ", std::quoted(argskey));
}
args = event[argskey];
}
}
return ParamsDispatchEntry::call(desc, args, fromMap, argskey);
}
LLSD getMetadata() const override
{
LLSD array(LLSD::emptyArray());
// Resize to number of arguments required
if (mArity)
array[mArity - 1] = LLSD();
llassert_always(array.size() == mArity);
meta["required"] = array;
return meta;
return llsd::map("required", array);
}
};
@ -485,11 +501,11 @@ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::Pa
*/
struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
{
MapParamsDispatchEntry(const std::string& name, const std::string& desc,
const invoker_function& func,
MapParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
const std::string& desc, const invoker_function& func,
const LLSD& params, const LLSD& defaults):
ParamsDispatchEntry(desc, func),
mMapper(name, params, defaults),
ParamsDispatchEntry(parent, name, desc, func),
mMapper(parent, name, params, defaults),
mRequired(LLSD::emptyMap())
{
// Build the set of all param keys, then delete the ones that are
@ -532,18 +548,27 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para
LLSD mRequired;
LLSD mOptional;
virtual void call(const std::string& desc, const LLSD& event) const
LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override
{
// Just convert from LLSD::Map to LLSD::Array using mMapper, then pass
// to base-class call() method.
ParamsDispatchEntry::call(desc, mMapper.map(event));
// by default, pass the whole event as the arguments map
LLSD args{ event };
// Were we called by one of the (event) methods (instead of the (name,
// event) methods), do we have an argskey, and does the incoming event
// have that key?
if (fromMap && (! argskey.empty()) && event.has(argskey))
{
// if so, extract the value of argskey from the incoming event,
// and use that as the arguments map
args = event[argskey];
}
// Now convert args from LLSD map to LLSD array using mMapper, then
// pass to base-class call() method.
return ParamsDispatchEntry::call(desc, mMapper.map(args), fromMap, argskey);
}
virtual LLSD addMetadata(LLSD meta) const
LLSD getMetadata() const override
{
meta["required"] = mRequired;
meta["optional"] = mOptional;
return meta;
return llsd::map("required", mRequired, "optional", mOptional);
}
};
@ -552,9 +577,9 @@ void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name,
const invoker_function& invoker,
LLSD::Integer arity)
{
mDispatch.insert(
DispatchMap::value_type(name, DispatchMap::mapped_type(
new ArrayParamsDispatchEntry(desc, invoker, arity))));
mDispatch.emplace(
name,
new ArrayParamsDispatchEntry(this, "", desc, invoker, arity));
}
void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,
@ -563,25 +588,25 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,
const LLSD& params,
const LLSD& defaults)
{
mDispatch.insert(
DispatchMap::value_type(name, DispatchMap::mapped_type(
new MapParamsDispatchEntry(name, desc, invoker, params, defaults))));
// Pass instance info as well as this entry name for error messages.
mDispatch.emplace(
name,
new MapParamsDispatchEntry(this, "", desc, invoker, params, defaults));
}
/// Register a callable by name
void LLEventDispatcher::add(const std::string& name, const std::string& desc,
const Callable& callable, const LLSD& required)
void LLEventDispatcher::addLLSD(const std::string& name, const std::string& desc,
const Callable& callable, const LLSD& required)
{
mDispatch.insert(
DispatchMap::value_type(name, DispatchMap::mapped_type(
new LLSDDispatchEntry(desc, callable, required))));
mDispatch.emplace(name, new LLSDDispatchEntry(this, desc, callable, required));
}
void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const
void LLEventDispatcher::addFail(const std::string& name, const char* classname) const
{
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name
<< "): " << classname << " is not a subclass "
<< "of LLEventDispatcher" << LL_ENDL;
<< "): " << LLError::Log::demangle(classname)
<< " is not a subclass of LLEventDispatcher"
<< LL_ENDL;
}
/// Unregister a callable
@ -596,48 +621,105 @@ bool LLEventDispatcher::remove(const std::string& name)
return true;
}
/// Call a registered callable with an explicitly-specified name. If no
/// such callable exists, die with LL_ERRS.
void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
/// Call a registered callable with an explicitly-specified name. It is an
/// error if no such callable exists.
LLSD LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
{
if (! try_call(name, event))
{
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name
<< "' not found" << LL_ENDL;
}
}
/// Extract the @a key value from the incoming @a event, and call the
/// callable whose name is specified by that map @a key. If no such
/// callable exists, die with LL_ERRS.
void LLEventDispatcher::operator()(const LLSD& event) const
{
// This could/should be implemented in terms of the two-arg overload.
// However -- we can produce a more informative error message.
std::string name(event[mKey]);
if (! try_call(name, event))
{
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey
<< " value '" << name << "'" << LL_ENDL;
}
}
bool LLEventDispatcher::try_call(const LLSD& event) const
{
return try_call(event[mKey], event);
return try_call(std::string(), name, event);
}
bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const
{
DispatchMap::const_iterator found = mDispatch.find(name);
if (found == mDispatch.end())
try
{
try_call(std::string(), name, event);
return true;
}
// Note that we don't catch the generic DispatchError, only the specific
// DispatchMissing. try_call() only promises to return false if the
// specified callable name isn't found -- not for general errors.
catch (const DispatchMissing&)
{
return false;
}
}
/// Extract the @a key value from the incoming @a event, and call the callable
/// whose name is specified by that map @a key. It is an error if no such
/// callable exists.
LLSD LLEventDispatcher::operator()(const LLSD& event) const
{
return try_call(mKey, event[mKey], event);
}
bool LLEventDispatcher::try_call(const LLSD& event) const
{
try
{
try_call(mKey, event[mKey], event);
return true;
}
catch (const DispatchMissing&)
{
return false;
}
}
LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name,
const LLSD& event) const
{
if (name.empty())
{
if (key.empty())
{
callFail<DispatchError>("attempting to call with no name");
}
else
{
callFail<DispatchError>("no ", key);
}
}
DispatchMap::const_iterator found = mDispatch.find(name);
if (found == mDispatch.end())
{
// Here we were passed a non-empty name, but there's no registered
// callable with that name. This is the one case in which we throw
// DispatchMissing instead of the generic DispatchError.
// Distinguish the public method by which our caller reached here:
// key.empty() means the name was passed explicitly, non-empty means
// we extracted the name from the incoming event using that key.
if (key.empty())
{
callFail<DispatchMissing>(std::quoted(name), " not found");
}
else
{
callFail<DispatchMissing>("bad ", key, " value ", std::quoted(name));
}
}
// Found the name, so it's plausible to even attempt the call.
found->second->call(STRINGIZE("LLEventDispatcher(" << mDesc << ") calling '" << name << "'"),
event);
return true; // tell caller we were able to call
const char* delim = (key.empty()? "" : "=");
// append either "[key=name]" or just "[name]"
SetState transient(this, '[', key, delim, name, ']');
return found->second->call("", event, (! key.empty()), mArgskey);
}
template <typename EXCEPTION, typename... ARGS>
//static
[[noreturn]] void LLEventDispatcher::sCallFail(ARGS&&... args)
{
auto error = stringize(std::forward<ARGS>(args)...);
LL_WARNS("LLEventDispatcher") << error << LL_ENDL;
LLTHROW(EXCEPTION(error));
}
template <typename EXCEPTION, typename... ARGS>
[[noreturn]] void LLEventDispatcher::callFail(ARGS&&... args) const
{
// Describe this instance in addition to the error itself.
sCallFail<EXCEPTION>(*this, ": ", std::forward<ARGS>(args)...);
}
LLSD LLEventDispatcher::getMetadata(const std::string& name) const
@ -647,26 +729,243 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const
{
return LLSD();
}
LLSD meta;
LLSD meta{ found->second->getMetadata() };
meta["name"] = name;
meta["desc"] = found->second->mDesc;
return found->second->addMetadata(meta);
return meta;
}
LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key):
LLEventDispatcher(pumpname, key),
mPump(pumpname, true), // allow tweaking for uniqueness
mBoundListener(mPump.listen("self", boost::bind(&LLDispatchListener::process, this, _1)))
std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self)
{
// If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that.
// Also report whatever transient state is active.
return out << LLError::Log::classname(self) << '(' << self.mDesc << ')'
<< self.getState();
}
bool LLDispatchListener::process(const LLSD& event)
std::string LLEventDispatcher::getState() const
{
(*this)(event);
// default value of fiber_specific_ptr is nullptr, and ~SetState() reverts
// to that; infer empty string
if (! mState.get())
return {};
else
return *mState;
}
bool LLEventDispatcher::setState(SetState&, const std::string& state) const
{
// If SetState is instantiated at multiple levels of function call, ignore
// the lower-level call because the outer call presumably provides more
// context.
if (mState.get())
return false;
// Pass us empty string (a la ~SetState()) to reset to nullptr, else take
// a heap copy of the passed state string so we can delete it on
// subsequent reset().
mState.reset(state.empty()? nullptr : new std::string(state));
return true;
}
/*****************************************************************************
* LLDispatchListener
*****************************************************************************/
std::string LLDispatchListener::mReplyKey{ "reply" };
bool LLDispatchListener::process(const LLSD& event) const
{
// Decide what to do based on the incoming value of the specified dispatch
// key.
LLSD name{ event[getDispatchKey()] };
if (name.isMap())
{
call_map(name, event);
}
else if (name.isArray())
{
call_array(name, event);
}
else
{
call_one(name, event);
}
return false;
}
LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc):
mDesc(desc)
{}
void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const
{
LLSD result;
try
{
result = (*this)(event);
}
catch (const DispatchError& err)
{
if (! event.has(mReplyKey))
{
// Without a reply key, let the exception propagate.
throw;
}
// Here there was an error and the incoming event has mReplyKey. Reply
// with a map containing an "error" key explaining the problem.
return reply(llsd::map("error", err.what()), event);
}
// We seem to have gotten a valid result. But we don't know whether the
// registered callable is void or non-void. If it's void,
// LLEventDispatcher returned isUndefined(). Otherwise, try to send it
// back to our invoker.
if (result.isDefined())
{
if (! result.isMap())
{
// wrap the result in a map as the "data" key
result = llsd::map("data", result);
}
reply(result, event);
}
}
void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const
{
// LLSD map containing returned values
LLSD result;
// cache dispatch key
std::string key{ getDispatchKey() };
// collect any error messages here
std::ostringstream errors;
const char* delim = "";
for (const auto& pair : llsd::inMap(reqmap))
{
const LLSD::String& name{ pair.first };
const LLSD& args{ pair.second };
try
{
// in case of errors, tell user the dispatch key, the fact that
// we're processing a request map and the current key in that map
SetState(this, '[', key, '[', name, "]]");
// With this form, capture return value even if undefined:
// presence of the key in the response map can be used to detect
// which request keys succeeded.
result[name] = (*this)(name, args);
}
catch (const std::exception& err)
{
// Catch not only DispatchError, but any C++ exception thrown by
// the target callable. Collect exception name and message in
// 'errors'.
errors << delim << LLError::Log::classname(err) << ": " << err.what();
delim = "\n";
}
}
// so, were there any errors?
std::string error = errors.str();
if (! error.empty())
{
if (! event.has(mReplyKey))
{
// can't send reply, throw
sCallFail<DispatchError>(error);
}
else
{
// reply key present
result["error"] = error;
}
}
reply(result, event);
}
void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) const
{
// LLSD array containing returned values
LLSD results;
// cache the dispatch key
std::string key{ getDispatchKey() };
// arguments array, if present -- const because, if it's shorter than
// reqarray, we don't want to grow it
const LLSD argsarray{ event[getArgsKey()] };
// error message, if any
std::string error;
// classic index loop because we need the index
for (size_t i = 0, size = reqarray.size(); i < size; ++i)
{
const auto& reqentry{ reqarray[i] };
std::string name;
LLSD args;
if (reqentry.isString())
{
name = reqentry.asString();
args = argsarray[i];
}
else if (reqentry.isArray() && reqentry.size() == 2 && reqentry[0].isString())
{
name = reqentry[0].asString();
args = reqentry[1];
}
else
{
// reqentry isn't in either of the documented forms
error = stringize(*this, ": ", getDispatchKey(), '[', i, "] ",
reqentry, " unsupported");
break;
}
// reqentry is one of the valid forms, got name and args
try
{
// in case of errors, tell user the dispatch key, the fact that
// we're processing a request array, the current entry in that
// array and the corresponding callable name
SetState(this, '[', key, '[', i, "]=", name, ']');
// With this form, capture return value even if undefined
results.append((*this)(name, args));
}
catch (const std::exception& err)
{
// Catch not only DispatchError, but any C++ exception thrown by
// the target callable. Report the exception class as well as the
// error string.
error = stringize(LLError::Log::classname(err), ": ", err.what());
break;
}
}
LLSD result;
// was there an error?
if (! error.empty())
{
if (! event.has(mReplyKey))
{
// can't send reply, throw
sCallFail<DispatchError>(error);
}
else
{
// reply key present
result["error"] = error;
}
}
// wrap the results array as response map "data" key, as promised
if (results.isDefined())
{
result["data"] = results;
}
reply(result, event);
}
void LLDispatchListener::reply(const LLSD& reply, const LLSD& request) const
{
// Call sendReply() unconditionally: sendReply() itself tests whether the
// specified reply key is present in the incoming request, and does
// nothing if there's no such key.
sendReply(reply, request, mReplyKey);
}

File diff suppressed because it is too large Load Diff

View File

@ -435,16 +435,61 @@ public:
// generic type-appropriate store through mTarget, construct an
// LLSDParam<T> and store that, thus engaging LLSDParam's custom
// conversions.
mTarget = LLSDParam<T>(llsd::drill(event, mPath));
storeTarget(LLSDParam<T>(llsd::drill(event, mPath)));
return mConsume;
}
private:
// This method disambiguates LLStoreListener<LLSD>. Directly assigning
// some_LLSD_var = LLSDParam<LLSD>(some_LLSD_value);
// is problematic because the compiler has too many choices: LLSD has
// multiple assignment operator overloads, and LLSDParam<LLSD> has a
// templated conversion operator. But LLSDParam<LLSD> can convert to a
// (const LLSD&) parameter, and LLSD::operator=(const LLSD&) works.
void storeTarget(const T& value)
{
mTarget = value;
}
T& mTarget;
const LLSD mPath;
const bool mConsume;
};
/**
* LLVarHolder bundles a target variable of the specified type. We use it as a
* base class so the target variable will be fully constructed by the time a
* subclass constructor tries to pass a reference to some other base class.
*/
template <typename T>
struct LLVarHolder
{
T mVar;
};
/**
* LLCaptureListener isa LLStoreListener that bundles the target variable of
* interest.
*/
template <typename T>
class LLCaptureListener: public LLVarHolder<T>,
public LLStoreListener<T>
{
private:
using holder = LLVarHolder<T>;
using super = LLStoreListener<T>;
public:
LLCaptureListener(const LLSD& path=LLSD(), bool consume=false):
super(*this, holder::mVar, path, consume)
{}
void set(T&& newval=T()) { holder::mVar = std::forward<T>(newval); }
const T& get() const { return holder::mVar; }
operator const T&() { return holder::mVar; }
};
/*****************************************************************************
* LLEventLogProxy
*****************************************************************************/

View File

@ -68,19 +68,78 @@
LLEventPumps::LLEventPumps():
mFactories
{
{ "LLEventStream", [](const std::string& name, bool tweak)
{ "LLEventStream", [](const std::string& name, bool tweak, const std::string& /*type*/)
{ return new LLEventStream(name, tweak); } },
{ "LLEventMailDrop", [](const std::string& name, bool tweak)
{ "LLEventMailDrop", [](const std::string& name, bool tweak, const std::string& /*type*/)
{ return new LLEventMailDrop(name, tweak); } }
},
mTypes
{
// LLEventStream is the default for obtain(), so even if somebody DOES
// call obtain("placeholder"), this sample entry won't break anything.
{ "placeholder", "LLEventStream" }
// { "placeholder", "LLEventStream" }
}
{}
bool LLEventPumps::registerTypeFactory(const std::string& type, const TypeFactory& factory)
{
auto found = mFactories.find(type);
// can't re-register a TypeFactory for a type name that's already registered
if (found != mFactories.end())
return false;
// doesn't already exist, go ahead and register
mFactories[type] = factory;
return true;
}
void LLEventPumps::unregisterTypeFactory(const std::string& type)
{
auto found = mFactories.find(type);
if (found != mFactories.end())
mFactories.erase(found);
}
bool LLEventPumps::registerPumpFactory(const std::string& name, const PumpFactory& factory)
{
// Do we already have a pump by this name?
if (mPumpMap.find(name) != mPumpMap.end())
return false;
// Do we already have an override for this pump name?
if (mTypes.find(name) != mTypes.end())
return false;
// Leverage the two-level lookup implemented by mTypes (pump name -> type
// name) and mFactories (type name -> factory). We could instead create a
// whole separate (pump name -> factory) map, and look in both; or we
// could change mTypes to (pump name -> factory) and, for typical type-
// based lookups, use a "factory" that looks up the real factory in
// mFactories. But this works, and we don't expect many calls to make() -
// either explicit or implicit via obtain().
// Create a bogus type name extremely unlikely to collide with an actual type.
static std::string nul(1, '\0');
std::string type_name{ nul + name };
mTypes[name] = type_name;
// TypeFactory is called with (name, tweak, type), whereas PumpFactory
// accepts only name. We could adapt with std::bind(), but this lambda
// does the trick.
mFactories[type_name] =
[factory]
(const std::string& name, bool /*tweak*/, const std::string& /*type*/)
{ return factory(name); };
return true;
}
void LLEventPumps::unregisterPumpFactory(const std::string& name)
{
auto tfound = mTypes.find(name);
if (tfound != mTypes.end())
{
auto ffound = mFactories.find(tfound->second);
if (ffound != mFactories.end())
{
mFactories.erase(ffound);
}
mTypes.erase(tfound);
}
}
LLEventPump& LLEventPumps::obtain(const std::string& name)
{
PumpMap::iterator found = mPumpMap.find(name);
@ -114,7 +173,7 @@ LLEventPump& LLEventPumps::make(const std::string& name, bool tweak,
// Passing an unrecognized type name is a no-no
LLTHROW(BadType(type));
}
auto newInstance = (found->second)(name, tweak);
auto newInstance = (found->second)(name, tweak, type);
// LLEventPump's constructor implicitly registers each new instance in
// mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll
// delete it later.

View File

@ -268,6 +268,45 @@ public:
LLEventPump& make(const std::string& name, bool tweak=false,
const std::string& type=std::string());
/// function passed to registerTypeFactory()
typedef std::function<LLEventPump*(const std::string& name, bool tweak, const std::string& type)> TypeFactory;
/**
* Register a TypeFactory for use with make(). When make() is called with
* the specified @a type string, call @a factory(name, tweak, type) to
* instantiate it.
*
* Returns true if successfully registered, false if there already exists
* a TypeFactory for the specified @a type name.
*/
bool registerTypeFactory(const std::string& type, const TypeFactory& factory);
void unregisterTypeFactory(const std::string& type);
/// function passed to registerPumpFactory()
typedef std::function<LLEventPump*(const std::string&)> PumpFactory;
/**
* Register a PumpFactory for use with obtain(). When obtain() is called
* with the specified @a name string, if an LLEventPump with the specified
* @a name doesn't already exist, call @a factory(name) to instantiate it.
*
* Returns true if successfully registered, false if there already exists
* a factory override for the specified @a name.
*
* PumpFactory does not support @a tweak because it's only called when
* <i>that particular</i> @a name is passed to obtain(). Bear in mind that
* <tt>obtain(name)</tt> might still bypass the caller's PumpFactory for a
* couple different reasons:
*
* * registerPumpFactory() returns false because there's already a factory
* override for the specified @name
* * between a successful <tt>registerPumpFactory(name)</tt> call (returns
* true) and a call to <tt>obtain(name)</tt>, someone explicitly
* instantiated an LLEventPump(name), so obtain(name) returned that.
*/
bool registerPumpFactory(const std::string& name, const PumpFactory& factory);
void unregisterPumpFactory(const std::string& name);
/**
* Find the named LLEventPump instance. If it exists post the message to it.
* If the pump does not exist, do nothing.
@ -325,13 +364,13 @@ testable:
typedef std::set<LLEventPump*> PumpSet;
PumpSet mOurPumps;
// for make(), map string type name to LLEventPump subclass factory function
typedef std::map<std::string, std::function<LLEventPump*(const std::string&, bool)>> PumpFactories;
typedef std::map<std::string, TypeFactory> TypeFactories;
// Data used by make().
// One might think mFactories and mTypes could reasonably be static. So
// they could -- if not for the fact that make() or obtain() might be
// called before this module's static variables have been initialized.
// This is why we use singletons in the first place.
PumpFactories mFactories;
TypeFactories mFactories;
// for obtain(), map desired string instance name to string type when
// obtain() must create the instance

View File

@ -29,11 +29,6 @@
#include "llframetimer.h"
// We don't bother building a stand alone lib; we just need to include the one source file for Tracy support
#if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY || LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY_FAST_TIMER
#include "TracyClient.cpp"
#endif // LL_PROFILER_CONFIGURATION
// Static members
//LLTimer LLFrameTimer::sInternalTimer;
U64 LLFrameTimer::sStartTotalTime = totalTime();

View File

@ -104,22 +104,26 @@ public:
return LockStatic()->mMap.size();
}
// snapshot of std::pair<const KEY, std::shared_ptr<T>> pairs
class snapshot
// snapshot of std::pair<const KEY, std::shared_ptr<SUBCLASS>> pairs, for
// some SUBCLASS derived from T
template <typename SUBCLASS>
class snapshot_of
{
// It's very important that what we store in this snapshot are
// weak_ptrs, NOT shared_ptrs. That's how we discover whether any
// instance has been deleted during the lifespan of a snapshot.
typedef std::vector<std::pair<const KEY, weak_t>> VectorType;
// Dereferencing our iterator produces a std::shared_ptr for each
// instance that still exists. Since we store weak_ptrs, that involves
// two chained transformations:
// Dereferencing the iterator we publish produces a
// std::shared_ptr<SUBCLASS> for each instance that still exists.
// Since we store weak_ptr<T>, that involves two chained
// transformations:
// - a transform_iterator to lock the weak_ptr and return a shared_ptr
// - a filter_iterator to skip any shared_ptr that has become invalid.
// - a filter_iterator to skip any shared_ptr<T> that has become
// invalid or references any T instance that isn't SUBCLASS.
// It is very important that we filter lazily, that is, during
// traversal. Any one of our stored weak_ptrs might expire during
// traversal.
typedef std::pair<const KEY, ptr_t> strong_pair;
typedef std::pair<const KEY, std::shared_ptr<SUBCLASS>> strong_pair;
// Note for future reference: nat has not yet had any luck (up to
// Boost 1.67) trying to use boost::transform_iterator with a hand-
// coded functor, only with actual functions. In my experience, an
@ -127,7 +131,7 @@ public:
// result_type typedef. But this works.
static strong_pair strengthen(typename VectorType::value_type& pair)
{
return { pair.first, pair.second.lock() };
return { pair.first, std::dynamic_pointer_cast<SUBCLASS>(pair.second.lock()) };
}
static bool dead_skipper(const strong_pair& pair)
{
@ -135,7 +139,7 @@ public:
}
public:
snapshot():
snapshot_of():
// populate our vector with a snapshot of (locked!) InstanceMap
// note, this assigns pair<KEY, shared_ptr> to pair<KEY, weak_ptr>
mData(mLock->mMap.begin(), mLock->mMap.end())
@ -184,44 +188,51 @@ public:
#endif // LL_WINDOWS
VectorType mData;
};
using snapshot = snapshot_of<T>;
// iterate over this for references to each instance
class instance_snapshot: public snapshot
// iterate over this for references to each SUBCLASS instance
template <typename SUBCLASS>
class instance_snapshot_of: public snapshot_of<SUBCLASS>
{
private:
static T& instance_getter(typename snapshot::iterator::reference pair)
using super = snapshot_of<SUBCLASS>;
static T& instance_getter(typename super::iterator::reference pair)
{
return *pair.second;
}
public:
typedef boost::transform_iterator<decltype(instance_getter)*,
typename snapshot::iterator> iterator;
iterator begin() { return iterator(snapshot::begin(), instance_getter); }
iterator end() { return iterator(snapshot::end(), instance_getter); }
typename super::iterator> iterator;
iterator begin() { return iterator(super::begin(), instance_getter); }
iterator end() { return iterator(super::end(), instance_getter); }
void deleteAll()
{
for (auto it(snapshot::begin()), end(snapshot::end()); it != end; ++it)
for (auto it(super::begin()), end(super::end()); it != end; ++it)
{
delete it->second.get();
}
}
};
};
using instance_snapshot = instance_snapshot_of<T>;
// iterate over this for each key
class key_snapshot: public snapshot
template <typename SUBCLASS>
class key_snapshot_of: public snapshot_of<SUBCLASS>
{
private:
static KEY key_getter(typename snapshot::iterator::reference pair)
using super = snapshot_of<SUBCLASS>;
static KEY key_getter(typename super::iterator::reference pair)
{
return pair.first;
}
public:
typedef boost::transform_iterator<decltype(key_getter)*,
typename snapshot::iterator> iterator;
iterator begin() { return iterator(snapshot::begin(), key_getter); }
iterator end() { return iterator(snapshot::end(), key_getter); }
typename super::iterator> iterator;
iterator begin() { return iterator(super::begin(), key_getter); }
iterator end() { return iterator(super::end(), key_getter); }
};
using key_snapshot = key_snapshot_of<T>;
static ptr_t getInstance(const KEY& k)
{
@ -368,22 +379,25 @@ public:
return LockStatic()->mSet.size();
}
// snapshot of std::shared_ptr<T> pointers
class snapshot
// snapshot of std::shared_ptr<SUBCLASS> pointers
template <typename SUBCLASS>
class snapshot_of
{
// It's very important that what we store in this snapshot are
// weak_ptrs, NOT shared_ptrs. That's how we discover whether any
// instance has been deleted during the lifespan of a snapshot.
typedef std::vector<weak_t> VectorType;
// Dereferencing our iterator produces a std::shared_ptr for each
// instance that still exists. Since we store weak_ptrs, that involves
// two chained transformations:
// Dereferencing the iterator we publish produces a
// std::shared_ptr<SUBCLASS> for each instance that still exists.
// Since we store weak_ptrs, that involves two chained
// transformations:
// - a transform_iterator to lock the weak_ptr and return a shared_ptr
// - a filter_iterator to skip any shared_ptr that has become invalid.
typedef std::shared_ptr<T> strong_ptr;
// - a filter_iterator to skip any shared_ptr that has become invalid
// or references any T instance that isn't SUBCLASS.
typedef std::shared_ptr<SUBCLASS> strong_ptr;
static strong_ptr strengthen(typename VectorType::value_type& ptr)
{
return ptr.lock();
return std::dynamic_pointer_cast<SUBCLASS>(ptr.lock());
}
static bool dead_skipper(const strong_ptr& ptr)
{
@ -391,7 +405,7 @@ public:
}
public:
snapshot():
snapshot_of():
// populate our vector with a snapshot of (locked!) InstanceSet
// note, this assigns stored shared_ptrs to weak_ptrs for snapshot
mData(mLock->mSet.begin(), mLock->mSet.end())
@ -437,22 +451,33 @@ public:
#endif // LL_WINDOWS
VectorType mData;
};
using snapshot = snapshot_of<T>;
// iterate over this for references to each instance
struct instance_snapshot: public snapshot
template <typename SUBCLASS>
class instance_snapshot_of: public snapshot_of<SUBCLASS>
{
typedef boost::indirect_iterator<typename snapshot::iterator> iterator;
iterator begin() { return iterator(snapshot::begin()); }
iterator end() { return iterator(snapshot::end()); }
private:
using super = snapshot_of<SUBCLASS>;
public:
typedef boost::indirect_iterator<typename super::iterator> iterator;
iterator begin() { return iterator(super::begin()); }
iterator end() { return iterator(super::end()); }
void deleteAll()
{
for (auto it(snapshot::begin()), end(snapshot::end()); it != end; ++it)
for (auto it(super::begin()), end(super::end()); it != end; ++it)
{
delete it->get();
}
}
};
using instance_snapshot = instance_snapshot_of<T>;
// key_snapshot_of isn't really meaningful, but define it anyway to avoid
// requiring two different LLInstanceTrackerSubclass implementations.
template <typename SUBCLASS>
using key_snapshot_of = instance_snapshot_of<SUBCLASS>;
protected:
LLInstanceTracker()

View File

@ -0,0 +1,98 @@
/**
* @file llinstancetrackersubclass.h
* @author Nat Goodspeed
* @date 2022-12-09
* @brief Intermediate class to get subclass-specific types from
* LLInstanceTracker instance-retrieval methods.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_LLINSTANCETRACKERSUBCLASS_H)
#define LL_LLINSTANCETRACKERSUBCLASS_H
#include <memory> // std::shared_ptr, std::weak_ptr
/**
* Derive your subclass S of a subclass T of LLInstanceTracker<T> from
* LLInstanceTrackerSubclass<S, T> to perform appropriate downcasting and
* filtering for LLInstanceTracker access methods.
*
* LLInstanceTracker<T> uses CRTP, so that getWeak(), getInstance(), snapshot
* and instance_snapshot return pointers and references to T. The trouble is
* that subclasses T0 and T1 derived from T also get pointers and references
* to their base class T, requiring explicit downcasting. Moreover,
* T0::getInstance() shouldn't find an instance of any T subclass other than
* T0. Nor should T0::snapshot.
*
* @code
* class Tracked: public LLInstanceTracker<Tracked, std::string>
* {
* private:
* using super = LLInstanceTracker<Tracked, std::string>;
* public:
* Tracked(const std::string& name): super(name) {}
* // All references to Tracked::ptr_t, Tracked::getInstance() etc.
* // appropriately use Tracked.
* // ...
* };
*
* // But now we derive SubTracked from Tracked. We need SubTracked::ptr_t,
* // SubTracked::getInstance() etc. to use SubTracked, not Tracked.
* // This LLInstanceTrackerSubclass specialization is itself derived from
* // Tracked.
* class SubTracked: public LLInstanceTrackerSubclass<SubTracked, Tracked>
* {
* private:
* using super = LLInstanceTrackerSubclass<SubTracked, Tracked>;
* public:
* // LLInstanceTrackerSubclass's constructor forwards to Tracked's.
* SubTracked(const std::string& name): super(name) {}
* // SubTracked::getInstance() returns std::shared_ptr<SubTracked>, etc.
* // ...
* @endcode
*/
template <typename SUBCLASS, typename T>
class LLInstanceTrackerSubclass: public T
{
public:
using ptr_t = std::shared_ptr<SUBCLASS>;
using weak_t = std::weak_ptr<SUBCLASS>;
// forward any constructor call to the corresponding T ctor
template <typename... ARGS>
LLInstanceTrackerSubclass(ARGS&&... args):
T(std::forward<ARGS>(args)...)
{}
weak_t getWeak()
{
// call base-class getWeak(), try to lock, downcast to SUBCLASS
return std::dynamic_pointer_cast<SUBCLASS>(T::getWeak().lock());
}
template <typename KEY>
static ptr_t getInstance(const KEY& k)
{
return std::dynamic_pointer_cast<SUBCLASS>(T::getInstance(k));
}
using snapshot = typename T::template snapshot_of<SUBCLASS>;
using instance_snapshot = typename T::template instance_snapshot_of<SUBCLASS>;
using key_snapshot = typename T::template key_snapshot_of<SUBCLASS>;
static size_t instanceCount()
{
// T::instanceCount() lies because our snapshot, et al., won't
// necessarily return all the T instances -- only those that are also
// SUBCLASS instances. Count those.
size_t count = 0;
for (const auto& pair : snapshot())
++count;
return count;
}
};
#endif /* ! defined(LL_LLINSTANCETRACKERSUBCLASS_H) */

View File

@ -340,11 +340,28 @@ public:
}
else
{
// The LLSD object we got from our stream contains the keys we
// need.
LLEventPumps::instance().obtain(data["pump"]).post(data["data"]);
// Block calls to this method; resetting mBlocker unblocks calls
// to the other method.
try
{
// The LLSD object we got from our stream contains the
// keys we need.
LLEventPumps::instance().obtain(data["pump"]).post(data["data"]);
}
catch (const std::exception& err)
{
// No plugin should be allowed to crash the viewer by
// driving an exception -- intentionally or not.
LOG_UNHANDLED_EXCEPTION(stringize("handling request ", data));
// Whether or not the plugin added a "reply" key to the
// request, send a reply. We happen to know who originated
// this request, and the reply LLEventPump of interest.
// Not our problem if the plugin ignores the reply event.
data["reply"] = mReplyPump.getName();
sendReply(llsd::map("error",
stringize(LLError::Log::classname(err), ": ", err.what())),
data);
}
// Block calls to this method; resetting mBlocker unblocks
// calls to the other method.
mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection));
// Go check for any more pending events in the buffer.
if (childout.size())

View File

@ -14,14 +14,16 @@
// associated header
#include "llleaplistener.h"
// STL headers
#include <map>
#include <algorithm> // std::find_if
#include <functional>
#include <map>
#include <set>
// std headers
// external library headers
#include <boost/foreach.hpp>
// other Linden headers
#include "lluuid.h"
#include "lazyeventapi.h"
#include "llsdutil.h"
#include "lluuid.h"
#include "stringize.h"
/*****************************************************************************
@ -110,7 +112,7 @@ LLLeapListener::~LLLeapListener()
// value_type, and Bad Things would happen if you copied an
// LLTempBoundListener. (Destruction of the original would disconnect the
// listener, invalidating every stored connection.)
BOOST_FOREACH(ListenersMap::value_type& pair, mListeners)
for (ListenersMap::value_type& pair : mListeners)
{
pair.second.disconnect();
}
@ -208,31 +210,65 @@ void LLLeapListener::getAPIs(const LLSD& request) const
{
Response reply(LLSD(), request);
// first, traverse existing LLEventAPI instances
std::set<std::string> instances;
for (auto& ea : LLEventAPI::instance_snapshot())
{
LLSD info;
info["desc"] = ea.getDesc();
reply[ea.getName()] = info;
// remember which APIs are actually instantiated
instances.insert(ea.getName());
reply[ea.getName()] = llsd::map("desc", ea.getDesc());
}
// supplement that with *potential* instances: that is, instances of
// LazyEventAPI that can each instantiate an LLEventAPI on demand
for (const auto& lea : LL::LazyEventAPIBase::instance_snapshot())
{
// skip any LazyEventAPI that's already instantiated its LLEventAPI
if (instances.find(lea.getName()) == instances.end())
{
reply[lea.getName()] = llsd::map("desc", lea.getDesc());
}
}
}
// Because LazyEventAPI deliberately mimics LLEventAPI's query API, this
// function can be passed either -- even though they're unrelated types.
template <typename API>
void reportAPI(LLEventAPI::Response& reply, const API& api)
{
reply["name"] = api.getName();
reply["desc"] = api.getDesc();
reply["key"] = api.getDispatchKey();
LLSD ops;
for (const auto& namedesc : api)
{
ops.append(api.getMetadata(namedesc.first));
}
reply["ops"] = ops;
}
void LLLeapListener::getAPI(const LLSD& request) const
{
Response reply(LLSD(), request);
auto found = LLEventAPI::getInstance(request["api"]);
if (found)
// check first among existing LLEventAPI instances
auto foundea = LLEventAPI::getInstance(request["api"]);
if (foundea)
{
reply["name"] = found->getName();
reply["desc"] = found->getDesc();
reply["key"] = found->getDispatchKey();
LLSD ops;
for (LLEventAPI::const_iterator oi(found->begin()), oend(found->end());
oi != oend; ++oi)
reportAPI(reply, *foundea);
}
else
{
// Here the requested LLEventAPI doesn't yet exist, but do we have a
// registered LazyEventAPI for it?
LL::LazyEventAPIBase::instance_snapshot snap;
auto foundlea = std::find_if(snap.begin(), snap.end(),
[api = request["api"].asString()]
(const auto& lea)
{ return (lea.getName() == api); });
if (foundlea != snap.end())
{
ops.append(found->getMetadata(oi->first));
reportAPI(reply, *foundlea);
}
reply["ops"] = ops;
}
}

View File

@ -35,6 +35,7 @@
# include <sys/types.h>
# include <mach/task.h>
# include <mach/mach_init.h>
#include <mach/mach_host.h>
#elif LL_LINUX
# include <unistd.h>
#endif
@ -109,6 +110,50 @@ void LLMemory::updateMemoryInfo()
{
sAvailPhysicalMemInKB = U32Kilobytes(0);
}
#elif defined(LL_DARWIN)
task_vm_info info;
mach_msg_type_number_t infoCount = TASK_VM_INFO_COUNT;
// MACH_TASK_BASIC_INFO reports the same resident_size, but does not tell us the reusable bytes or phys_footprint.
if (task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast<task_info_t>(&info), &infoCount) == KERN_SUCCESS)
{
// Our Windows definition of PagefileUsage is documented by Microsoft as "the total amount of
// memory that the memory manager has committed for a running process", which is rss.
sAllocatedPageSizeInKB = U32Bytes(info.resident_size);
// Activity Monitor => Inspect Process => Real Memory Size appears to report resident_size
// Activity monitor => main window memory column appears to report phys_footprint, which spot checks as at least 30% less.
// I think that is because of compression, which isn't going to give us a consistent measurement. We want uncompressed totals.
//
// In between is resident_size - reusable. This is what Chrome source code uses, with source comments saying it is 'the "Real Memory" value
// reported for the app by the Memory Monitor in Instruments.' It is still about 8% bigger than phys_footprint.
//
// (On Windows, we use WorkingSetSize.)
sAllocatedMemInKB = U32Bytes(info.resident_size - info.reusable);
}
else
{
LL_WARNS() << "task_info failed" << LL_ENDL;
}
// Total installed and available physical memory are properties of the host, not just our process.
vm_statistics64_data_t vmstat;
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
mach_port_t host = mach_host_self();
vm_size_t page_size;
host_page_size(host, &page_size);
kern_return_t result = host_statistics64(host, HOST_VM_INFO64, reinterpret_cast<host_info_t>(&vmstat), &count);
if (result == KERN_SUCCESS) {
// This is what Chrome reports as 'the "Physical Memory Free" value reported by the Memory Monitor in Instruments.'
// Note though that inactive pages are not included here and not yet free, but could become so under memory pressure.
sAvailPhysicalMemInKB = U32Bytes(vmstat.free_count * page_size);
sMaxPhysicalMemInKB = LLMemoryInfo::getHardwareMemSize();
}
else
{
LL_WARNS() << "task_info failed" << LL_ENDL;
}
#else
//not valid for other systems for now.
sAllocatedMemInKB = U64Bytes(LLMemory::getCurrentRSS());

View File

@ -36,7 +36,8 @@
//============================================================================
#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO)
//#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO)
#define MUTEX_DEBUG 0 //disable mutex debugging as it's interfering with profiles
#if MUTEX_DEBUG
#include <map>
@ -61,7 +62,7 @@ protected:
mutable LLThread::id_t mLockingThread;
#if MUTEX_DEBUG
std::map<LLThread::id_t, BOOL> mIsLocked;
std::unordered_map<LLThread::id_t, BOOL> mIsLocked;
#endif
};

View File

@ -341,4 +341,28 @@ private:
bool mStayUnique;
};
// boost hash adapter
template <class Type>
struct boost::hash<LLPointer<Type>>
{
typedef LLPointer<Type> argument_type;
typedef std::size_t result_type;
result_type operator()(argument_type const& s) const
{
return (std::size_t) s.get();
}
};
// Adapt boost hash to std hash
namespace std
{
template<class Type> struct hash<LLPointer<Type>>
{
std::size_t operator()(LLPointer<Type> const& s) const noexcept
{
return boost::hash<LLPointer<Type>>()(s);
}
};
}
#endif

View File

@ -86,8 +86,12 @@ extern thread_local bool gProfilerEnabled;
#define TRACY_ONLY_IPV4 1
#include "Tracy.hpp"
// Mutually exclusive with detailed memory tracing
// Enable OpenGL profiling
#define LL_PROFILER_ENABLE_TRACY_OPENGL 0
// Enable RenderDoc labeling
#define LL_PROFILER_ENABLE_RENDER_DOC 0
#endif
#if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY
@ -104,14 +108,13 @@ extern thread_local bool gProfilerEnabled;
#define LL_PROFILE_ZONE_ERR(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0XFF0000 ) // RGB yellow
#define LL_PROFILE_ZONE_INFO(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0X00FFFF ) // RGB cyan
#define LL_PROFILE_ZONE_WARN(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0x0FFFF00 ) // RGB red
#define LL_PROFILE_ALLOC(ptr, size) TracyAlloc(ptr, size)
#define LL_PROFILE_FREE(ptr) TracyFree(ptr)
#endif
#if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_FAST_TIMER
#define LL_PROFILER_FRAME_END
#define LL_PROFILER_SET_THREAD_NAME( name ) (void)(name)
#define LL_RECORD_BLOCK_TIME(name) const LLTrace::BlockTimer& LL_GLUE_TOKENS(block_time_recorder, __LINE__)(LLTrace::timeThisBlock(name)); (void)LL_GLUE_TOKENS(block_time_recorder, __LINE__);
#define LL_PROFILE_ZONE_NAMED(name) // LL_PROFILE_ZONE_NAMED is a no-op when Tracy is disabled
#define LL_PROFILE_ZONE_NAMED_COLOR(name,color) // LL_PROFILE_ZONE_NAMED_COLOR is a no-op when Tracy is disabled
#define LL_PROFILE_ZONE_SCOPED // LL_PROFILE_ZONE_SCOPED is a no-op when Tracy is disabled
#define LL_PROFILE_ZONE_COLOR(name,color) // LL_RECORD_BLOCK_TIME(name)
@ -121,8 +124,6 @@ extern thread_local bool gProfilerEnabled;
#define LL_PROFILE_ZONE_ERR(name) (void)(name); // Not supported
#define LL_PROFILE_ZONE_INFO(name) (void)(name); // Not supported
#define LL_PROFILE_ZONE_WARN(name) (void)(name); // Not supported
#define LL_PROFILE_ALLOC(ptr, size) (void)(ptr); (void)(size);
#define LL_PROFILE_FREE(ptr) (void)(ptr);
#endif
#if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY_FAST_TIMER
#define LL_PROFILER_FRAME_END FrameMark
@ -138,14 +139,45 @@ extern thread_local bool gProfilerEnabled;
#define LL_PROFILE_ZONE_ERR(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0XFF0000 ) // RGB yellow
#define LL_PROFILE_ZONE_INFO(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0X00FFFF ) // RGB cyan
#define LL_PROFILE_ZONE_WARN(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0x0FFFF00 ) // RGB red
#define LL_PROFILE_ALLOC(ptr, size) TracyAlloc(ptr, size)
#define LL_PROFILE_FREE(ptr) TracyFree(ptr)
#endif
#else
#define LL_PROFILER_FRAME_END
#define LL_PROFILER_SET_THREAD_NAME( name ) (void)(name)
#endif // LL_PROFILER
#if LL_PROFILER_ENABLE_TRACY_OPENGL
#define LL_PROFILE_GPU_ZONE(name) TracyGpuZone(name)
#define LL_PROFILE_GPU_ZONEC(name,color) TracyGpuZoneC(name,color)
#define LL_PROFILER_GPU_COLLECT TracyGpuCollect
#define LL_PROFILER_GPU_CONTEXT TracyGpuContext
// disable memory tracking (incompatible with GPU tracing
#define LL_PROFILE_ALLOC(ptr, size) (void)(ptr); (void)(size);
#define LL_PROFILE_FREE(ptr) (void)(ptr);
#else
#define LL_PROFILE_GPU_ZONE(name) (void)name;
#define LL_PROFILE_GPU_ZONEC(name,color) (void)name;(void)color;
#define LL_PROFILER_GPU_COLLECT
#define LL_PROFILER_GPU_CONTEXT
#define LL_LABEL_OBJECT_GL(type, name, length, label)
#if LL_PROFILER_CONFIGURATION > 1
#define LL_PROFILE_ALLOC(ptr, size) TracyAlloc(ptr, size)
#define LL_PROFILE_FREE(ptr) TracyFree(ptr)
#else
#define LL_PROFILE_ALLOC(ptr, size) (void)(ptr); (void)(size);
#define LL_PROFILE_FREE(ptr) (void)(ptr);
#endif
#endif
#if LL_PROFILER_ENABLE_RENDER_DOC
#define LL_LABEL_OBJECT_GL(type, name, length, label) glObjectLabel(type, name, length, label)
#else
#define LL_LABEL_OBJECT_GL(type, name, length, label)
#endif
#include "llprofilercategories.h"
#endif // LL_PROFILER_H

View File

@ -52,7 +52,7 @@
#define LL_PROFILER_CATEGORY_ENABLE_LOGGING 1
#define LL_PROFILER_CATEGORY_ENABLE_MATERIAL 1
#define LL_PROFILER_CATEGORY_ENABLE_MEDIA 1
#define LL_PROFILER_CATEGORY_ENABLE_MEMORY 1
#define LL_PROFILER_CATEGORY_ENABLE_MEMORY 0
#define LL_PROFILER_CATEGORY_ENABLE_NETWORK 1
#define LL_PROFILER_CATEGORY_ENABLE_OCTREE 1
#define LL_PROFILER_CATEGORY_ENABLE_PIPELINE 1

View File

@ -33,9 +33,12 @@
#include "llpointer.h"
#include "llrefcount.h" // LLRefCount
#include <boost/intrusive_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/type_traits/is_base_of.hpp>
#include <boost/type_traits/remove_pointer.hpp>
#include <boost/utility/enable_if.hpp>
#include <memory> // std::shared_ptr, std::unique_ptr
#include <type_traits>
/**
* LLPtrTo<TARGET>::type is either of two things:
@ -55,14 +58,14 @@ struct LLPtrTo
/// specialize for subclasses of LLRefCount
template <class T>
struct LLPtrTo<T, typename boost::enable_if< boost::is_base_of<LLRefCount, T> >::type>
struct LLPtrTo<T, typename std::enable_if< boost::is_base_of<LLRefCount, T>::value >::type>
{
typedef LLPointer<T> type;
};
/// specialize for subclasses of LLThreadSafeRefCount
template <class T>
struct LLPtrTo<T, typename boost::enable_if< boost::is_base_of<LLThreadSafeRefCount, T> >::type>
struct LLPtrTo<T, typename std::enable_if< boost::is_base_of<LLThreadSafeRefCount, T>::value >::type>
{
typedef LLPointer<T> type;
};
@ -83,4 +86,83 @@ struct LLRemovePointer< LLPointer<SOMECLASS> >
typedef SOMECLASS type;
};
namespace LL
{
/*****************************************************************************
* get_ref()
*****************************************************************************/
template <typename T>
struct GetRef
{
// return const ref or non-const ref, depending on whether we can bind
// a non-const lvalue ref to the argument
const auto& operator()(const T& obj) const { return obj; }
auto& operator()(T& obj) const { return obj; }
};
template <typename T>
struct GetRef<const T*>
{
const auto& operator()(const T* ptr) const { return *ptr; }
};
template <typename T>
struct GetRef<T*>
{
auto& operator()(T* ptr) const { return *ptr; }
};
template <typename T>
struct GetRef< LLPointer<T> >
{
auto& operator()(LLPointer<T> ptr) const { return *ptr; }
};
/// whether we're passed a pointer or a reference, return a reference
template <typename T>
auto& get_ref(T& ptr_or_ref)
{
return GetRef<typename std::decay<T>::type>()(ptr_or_ref);
}
template <typename T>
const auto& get_ref(const T& ptr_or_ref)
{
return GetRef<typename std::decay<T>::type>()(ptr_or_ref);
}
/*****************************************************************************
* get_ptr()
*****************************************************************************/
// if T is any pointer type we recognize, return it unchanged
template <typename T>
const T* get_ptr(const T* ptr) { return ptr; }
template <typename T>
T* get_ptr(T* ptr) { return ptr; }
template <typename T>
const std::shared_ptr<T>& get_ptr(const std::shared_ptr<T>& ptr) { return ptr; }
template <typename T>
const std::unique_ptr<T>& get_ptr(const std::unique_ptr<T>& ptr) { return ptr; }
template <typename T>
const boost::shared_ptr<T>& get_ptr(const boost::shared_ptr<T>& ptr) { return ptr; }
template <typename T>
const boost::intrusive_ptr<T>& get_ptr(const boost::intrusive_ptr<T>& ptr) { return ptr; }
template <typename T>
const LLPointer<T>& get_ptr(const LLPointer<T>& ptr) { return ptr; }
// T is not any pointer type we recognize, take a pointer to the parameter
template <typename T>
const T* get_ptr(const T& obj) { return &obj; }
template <typename T>
T* get_ptr(T& obj) { return &obj; }
} // namespace LL
#endif /* ! defined(LL_LLPTRTO_H) */

View File

@ -26,20 +26,26 @@
#include "linden_common.h"
#include "llqueuedthread.h"
#include <chrono>
#include "llstl.h"
#include "lltimer.h" // ms_sleep()
#include "lltracethreadrecorder.h"
#include "llmutex.h"
//============================================================================
// MAIN THREAD
LLQueuedThread::LLQueuedThread(const std::string& name, bool threaded, bool should_pause) :
LLThread(name),
mThreaded(threaded),
mIdleThread(TRUE),
mNextHandle(0),
mStarted(FALSE)
LLThread(name),
mIdleThread(TRUE),
mNextHandle(0),
mStarted(FALSE),
mThreaded(threaded),
mRequestQueue(name, 1024 * 1024)
{
llassert(threaded); // not threaded implementation is deprecated
mMainQueue = LL::WorkQueue::getInstance("mainloop");
if (mThreaded)
{
if(should_pause)
@ -69,6 +75,11 @@ void LLQueuedThread::shutdown()
unpause(); // MAIN THREAD
if (mThreaded)
{
if (mRequestQueue.size() == 0)
{
mRequestQueue.close();
}
S32 timeout = 100;
for ( ; timeout>0; timeout--)
{
@ -104,6 +115,8 @@ void LLQueuedThread::shutdown()
{
LL_WARNS() << "~LLQueuedThread() called with active requests: " << active_count << LL_ENDL;
}
mRequestQueue.close();
}
//----------------------------------------------------------------------------
@ -112,6 +125,7 @@ void LLQueuedThread::shutdown()
// virtual
size_t LLQueuedThread::update(F32 max_time_ms)
{
LL_PROFILE_ZONE_SCOPED;
if (!mStarted)
{
if (!mThreaded)
@ -125,29 +139,34 @@ size_t LLQueuedThread::update(F32 max_time_ms)
size_t LLQueuedThread::updateQueue(F32 max_time_ms)
{
F64 max_time = (F64)max_time_ms * .001;
LLTimer timer;
size_t pending = 1;
LL_PROFILE_ZONE_SCOPED;
// Frame Update
if (mThreaded)
{
pending = getPending();
if(pending > 0)
// schedule a call to threadedUpdate for every call to updateQueue
if (!isQuitting())
{
mRequestQueue.post([=]()
{
LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qt - update");
mIdleThread = FALSE;
threadedUpdate();
mIdleThread = TRUE;
}
);
}
if(getPending() > 0)
{
unpause();
}
unpause();
}
}
else
{
while (pending > 0)
{
pending = processNextRequest();
if (max_time && timer.getElapsedTimeF64() > max_time)
break;
}
mRequestQueue.runFor(std::chrono::microseconds((int) (max_time_ms*1000.f)));
threadedUpdate();
}
return pending;
return getPending();
}
void LLQueuedThread::incQueue()
@ -166,11 +185,7 @@ void LLQueuedThread::incQueue()
// May be called from any thread
size_t LLQueuedThread::getPending()
{
size_t res;
lockData();
res = mRequestQueue.size();
unlockData();
return res;
return mRequestQueue.size();
}
// MAIN thread
@ -195,35 +210,28 @@ void LLQueuedThread::waitOnPending()
// MAIN thread
void LLQueuedThread::printQueueStats()
{
lockData();
if (!mRequestQueue.empty())
U32 size = mRequestQueue.size();
if (size > 0)
{
QueuedRequest *req = *mRequestQueue.begin();
LL_INFOS() << llformat("Pending Requests:%d Current status:%d", mRequestQueue.size(), req->getStatus()) << LL_ENDL;
LL_INFOS() << llformat("Pending Requests:%d ", mRequestQueue.size()) << LL_ENDL;
}
else
{
LL_INFOS() << "Queued Thread Idle" << LL_ENDL;
}
unlockData();
}
// MAIN thread
LLQueuedThread::handle_t LLQueuedThread::generateHandle()
{
lockData();
while ((mNextHandle == nullHandle()) || (mRequestHash.find(mNextHandle)))
{
mNextHandle++;
}
const LLQueuedThread::handle_t res = mNextHandle++;
unlockData();
U32 res = ++mNextHandle;
return res;
}
// MAIN thread
bool LLQueuedThread::addRequest(QueuedRequest* req)
{
LL_PROFILE_ZONE_SCOPED;
if (mStatus == QUITTING)
{
return false;
@ -231,14 +239,14 @@ bool LLQueuedThread::addRequest(QueuedRequest* req)
lockData();
req->setStatus(STATUS_QUEUED);
mRequestQueue.insert(req);
mRequestHash.insert(req);
mRequestHash.insert(req);
#if _DEBUG
// LL_INFOS() << llformat("LLQueuedThread::Added req [%08d]",handle) << LL_ENDL;
#endif
unlockData();
incQueue();
llassert(!mDataLock->isSelfLocked());
mRequestQueue.post([this, req]() { processRequest(req); });
return true;
}
@ -246,6 +254,7 @@ bool LLQueuedThread::addRequest(QueuedRequest* req)
// MAIN thread
bool LLQueuedThread::waitForResult(LLQueuedThread::handle_t handle, bool auto_complete)
{
LL_PROFILE_ZONE_SCOPED;
llassert (handle != nullHandle());
bool res = false;
bool waspaused = isPaused();
@ -312,6 +321,7 @@ LLQueuedThread::status_t LLQueuedThread::getRequestStatus(handle_t handle)
void LLQueuedThread::abortRequest(handle_t handle, bool autocomplete)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
lockData();
QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
if (req)
@ -333,30 +343,9 @@ void LLQueuedThread::setFlags(handle_t handle, U32 flags)
unlockData();
}
void LLQueuedThread::setPriority(handle_t handle, U32 priority)
{
lockData();
QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
if (req)
{
if(req->getStatus() == STATUS_INPROGRESS)
{
// not in list
req->setPriority(priority);
}
else if(req->getStatus() == STATUS_QUEUED)
{
// remove from list then re-insert
llverify(mRequestQueue.erase(req) == 1);
req->setPriority(priority);
mRequestQueue.insert(req);
}
}
unlockData();
}
bool LLQueuedThread::completeRequest(handle_t handle)
{
LL_PROFILE_ZONE_SCOPED;
bool res = false;
lockData();
QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
@ -399,88 +388,120 @@ bool LLQueuedThread::check()
//============================================================================
// Runs on its OWN thread
size_t LLQueuedThread::processNextRequest()
void LLQueuedThread::processRequest(LLQueuedThread::QueuedRequest* req)
{
QueuedRequest *req;
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
mIdleThread = FALSE;
//threadedUpdate();
// Get next request from pool
lockData();
while(1)
if ((req->getFlags() & FLAG_ABORT) || (mStatus == QUITTING))
{
req = NULL;
if (mRequestQueue.empty())
LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - abort");
req->setStatus(STATUS_ABORTED);
req->finishRequest(false);
if (req->getFlags() & FLAG_AUTO_COMPLETE)
{
break;
}
req = *mRequestQueue.begin();
mRequestQueue.erase(mRequestQueue.begin());
if ((req->getFlags() & FLAG_ABORT) || (mStatus == QUITTING))
{
req->setStatus(STATUS_ABORTED);
req->finishRequest(false);
if (req->getFlags() & FLAG_AUTO_COMPLETE)
{
mRequestHash.erase(req);
req->deleteRequest();
mRequestHash.erase(req);
req->deleteRequest();
// check();
}
continue;
}
llassert_always(req->getStatus() == STATUS_QUEUED);
break;
unlockData();
}
U32 start_priority = 0 ;
if (req)
{
req->setStatus(STATUS_INPROGRESS);
start_priority = req->getPriority();
}
unlockData();
else
{
llassert_always(req->getStatus() == STATUS_QUEUED);
// This is the only place we will call req->setStatus() after
// it has initially been seet to STATUS_QUEUED, so it is
// safe to access req.
if (req)
{
// process request
bool complete = req->processRequest();
if (req)
{
req->setStatus(STATUS_INPROGRESS);
}
unlockData();
if (complete)
{
lockData();
req->setStatus(STATUS_COMPLETE);
req->finishRequest(true);
if (req->getFlags() & FLAG_AUTO_COMPLETE)
{
mRequestHash.erase(req);
req->deleteRequest();
// check();
}
unlockData();
}
else
{
lockData();
req->setStatus(STATUS_QUEUED);
mRequestQueue.insert(req);
unlockData();
if (mThreaded && start_priority < PRIORITY_NORMAL)
{
ms_sleep(1); // sleep the thread a little
}
}
LLTrace::get_thread_recorder()->pushToParent();
}
// This is the only place we will call req->setStatus() after
// it has initially been seet to STATUS_QUEUED, so it is
// safe to access req.
if (req)
{
// process request
bool complete = req->processRequest();
return getPending();
if (complete)
{
LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - complete");
lockData();
req->setStatus(STATUS_COMPLETE);
req->finishRequest(true);
if (req->getFlags() & FLAG_AUTO_COMPLETE)
{
mRequestHash.erase(req);
req->deleteRequest();
// check();
}
unlockData();
}
else
{
LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - retry");
//put back on queue and try again in 0.1ms
lockData();
req->setStatus(STATUS_QUEUED);
unlockData();
llassert(!mDataLock->isSelfLocked());
#if 0
// try again on next frame
// NOTE: tried using "post" with a time in the future, but this
// would invariably cause this thread to wait for a long time (10+ ms)
// while work is pending
bool ret = LL::WorkQueue::postMaybe(
mMainQueue,
[=]()
{
LL_PROFILE_ZONE_NAMED("processRequest - retry");
mRequestQueue.post([=]()
{
LL_PROFILE_ZONE_NAMED("processRequest - retry"); // <-- not redundant, track retry on both queues
processRequest(req);
});
});
llassert(ret);
#else
using namespace std::chrono_literals;
auto retry_time = LL::WorkQueue::TimePoint::clock::now() + 16ms;
mRequestQueue.post([=]
{
LL_PROFILE_ZONE_NAMED("processRequest - retry");
if (LL::WorkQueue::TimePoint::clock::now() < retry_time)
{
auto sleep_time = std::chrono::duration_cast<std::chrono::milliseconds>(retry_time - LL::WorkQueue::TimePoint::clock::now());
if (sleep_time.count() > 0)
{
ms_sleep(sleep_time.count());
}
}
processRequest(req);
});
#endif
}
}
}
mIdleThread = TRUE;
}
// virtual
bool LLQueuedThread::runCondition()
{
// mRunCondition must be locked here
if (mRequestQueue.empty() && mIdleThread)
if (mRequestQueue.size() == 0 && mIdleThread)
return false;
else
return true;
@ -494,18 +515,13 @@ void LLQueuedThread::run()
startThread();
mStarted = TRUE;
while (1)
/*while (1)
{
LL_PROFILE_ZONE_SCOPED;
// this will block on the condition until runCondition() returns true, the thread is unpaused, or the thread leaves the RUNNING state.
checkPause();
if (isQuitting())
{
LLTrace::get_thread_recorder()->pushToParent();
endThread();
break;
}
mIdleThread = FALSE;
threadedUpdate();
@ -514,12 +530,18 @@ void LLQueuedThread::run()
if (pending_work == 0)
{
//LL_PROFILE_ZONE_NAMED("LLQueuedThread - sleep");
mIdleThread = TRUE;
ms_sleep(1);
//ms_sleep(1);
}
//LLThread::yield(); // thread should yield after each request
}
}*/
mRequestQueue.runUntilClose();
endThread();
LL_INFOS() << "LLQueuedThread " << mName << " EXITING." << LL_ENDL;
}
// virtual
@ -539,10 +561,9 @@ void LLQueuedThread::threadedUpdate()
//============================================================================
LLQueuedThread::QueuedRequest::QueuedRequest(LLQueuedThread::handle_t handle, U32 priority, U32 flags) :
LLQueuedThread::QueuedRequest::QueuedRequest(LLQueuedThread::handle_t handle, U32 flags) :
LLSimpleHashEntry<LLQueuedThread::handle_t>(handle),
mStatus(STATUS_UNKNOWN),
mPriority(priority),
mFlags(flags)
{
}

View File

@ -36,6 +36,7 @@
#include "llthread.h"
#include "llsimplehash.h"
#include "workqueue.h"
//============================================================================
// Note: ~LLQueuedThread is O(N) N=# of queued threads, assumed to be small
@ -45,15 +46,6 @@ class LL_COMMON_API LLQueuedThread : public LLThread
{
//------------------------------------------------------------------------
public:
enum priority_t {
PRIORITY_IMMEDIATE = 0x7FFFFFFF,
PRIORITY_URGENT = 0x40000000,
PRIORITY_HIGH = 0x30000000,
PRIORITY_NORMAL = 0x20000000,
PRIORITY_LOW = 0x10000000,
PRIORITY_LOWBITS = 0x0FFFFFFF,
PRIORITY_HIGHBITS = 0x70000000
};
enum status_t {
STATUS_EXPIRED = -1,
STATUS_UNKNOWN = 0,
@ -82,28 +74,17 @@ public:
virtual ~QueuedRequest(); // use deleteRequest()
public:
QueuedRequest(handle_t handle, U32 priority, U32 flags = 0);
QueuedRequest(handle_t handle, U32 flags = 0);
status_t getStatus()
{
return mStatus;
}
U32 getPriority() const
{
return mPriority;
}
U32 getFlags() const
{
return mFlags;
}
bool higherPriority(const QueuedRequest& second) const
{
if ( mPriority == second.mPriority)
return mHashKey < second.mHashKey;
else
return mPriority > second.mPriority;
}
protected:
status_t setStatus(status_t newstatus)
{
@ -121,28 +102,11 @@ public:
virtual void finishRequest(bool completed); // Always called from thread after request has completed or aborted
virtual void deleteRequest(); // Only method to delete a request
void setPriority(U32 pri)
{
// Only do this on a request that is not in a queued list!
mPriority = pri;
};
protected:
LLAtomicBase<status_t> mStatus;
U32 mPriority;
U32 mFlags;
};
protected:
struct queued_request_less
{
bool operator()(const QueuedRequest* lhs, const QueuedRequest* rhs) const
{
return lhs->higherPriority(*rhs); // higher priority in front of queue (set)
}
};
//------------------------------------------------------------------------
public:
@ -167,7 +131,7 @@ private:
protected:
handle_t generateHandle();
bool addRequest(QueuedRequest* req);
size_t processNextRequest(void);
void processRequest(QueuedRequest* req);
void incQueue();
public:
@ -186,7 +150,6 @@ public:
status_t getRequestStatus(handle_t handle);
void abortRequest(handle_t handle, bool autocomplete);
void setFlags(handle_t handle, U32 flags);
void setPriority(handle_t handle, U32 priority);
bool completeRequest(handle_t handle);
// This is public for support classes like LLWorkerThread,
// but generally the methods above should be used.
@ -200,8 +163,10 @@ protected:
BOOL mStarted; // required when mThreaded is false to call startThread() from update()
LLAtomicBool mIdleThread; // request queue is empty (or we are quitting) and the thread is idle
typedef std::set<QueuedRequest*, queued_request_less> request_queue_t;
request_queue_t mRequestQueue;
//typedef std::set<QueuedRequest*, queued_request_less> request_queue_t;
//request_queue_t mRequestQueue;
LL::WorkQueue mRequestQueue;
LL::WorkQueue::weak_t mMainQueue;
enum { REQUEST_HASH_SIZE = 512 }; // must be power of 2
typedef LLSimpleHash<handle_t, REQUEST_HASH_SIZE> request_hash_t;

View File

@ -475,6 +475,7 @@ LLSDNotationParser::~LLSDNotationParser()
// virtual
S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
// map: { string:object, string:object }
// array: [ object, object, object ]
// undef: !
@ -734,6 +735,7 @@ S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) c
S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
// map: { string:object, string:object }
map = LLSD::emptyMap();
S32 parse_count = 0;
@ -794,6 +796,7 @@ S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) c
S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
// array: [ object, object, object ]
array = LLSD::emptyArray();
S32 parse_count = 0;
@ -833,6 +836,7 @@ S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array, S32 max_dept
bool LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
std::string value;
auto count = deserialize_string(istr, value, mMaxBytesLeft);
if(PARSE_FAILURE == count) return false;
@ -843,6 +847,7 @@ bool LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const
bool LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
// binary: b##"ff3120ab1"
// or: b(len)"..."
@ -945,6 +950,7 @@ LLSDBinaryParser::~LLSDBinaryParser()
// virtual
S32 LLSDBinaryParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
/**
* Undefined: '!'<br>
* Boolean: '1' for true '0' for false<br>

View File

@ -923,6 +923,8 @@ void LLSDXMLParser::parsePart(const char *buf, llssize len)
// virtual
S32 LLSDXMLParser::doParse(std::istream& input, LLSD& data, S32 max_depth) const
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
#ifdef XML_PARSER_PERFORMANCE_TESTS
XML_Timer timer( &parseTime );
#endif // XML_PARSER_PERFORMANCE_TESTS

View File

@ -1046,3 +1046,38 @@ LLSD llsd_shallow(LLSD value, LLSD filter)
return shallow;
}
LLSD LL::apply_llsd_fix(size_t arity, const LLSD& args)
{
// LLSD supports a number of types, two of which are aggregates: Map and
// Array. We don't try to support Map: supporting Map would seem to
// promise that we could somehow match the string key to 'func's parameter
// names. Uh sorry, maybe in some future version of C++ with reflection.
if (args.isMap())
{
LLTHROW(LL::apply_error("LL::apply(function, Map LLSD) unsupported"));
}
// We expect an LLSD array, but what the heck, treat isUndefined() as a
// zero-length array for calling a nullary 'func'.
if (args.isUndefined() || args.isArray())
{
// this works because LLSD().size() == 0
if (args.size() != arity)
{
LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), ",
args.size(), "-entry LLSD array)")));
}
return args;
}
// args is one of the scalar types
// scalar_LLSD.size() == 0, so don't test that here.
// You can pass a scalar LLSD only to a unary 'func'.
if (arity != 1)
{
LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), "
"LLSD ", LLSD::typeString(args.type()), ")")));
}
// make an array of it
return llsd::array(args);
}

View File

@ -29,8 +29,14 @@
#ifndef LL_LLSDUTIL_H
#define LL_LLSDUTIL_H
#include "apply.h" // LL::invoke()
#include "function_types.h" // LL::function_arity
#include "llsd.h"
#include <boost/functional/hash.hpp>
#include <cassert>
#include <memory> // std::shared_ptr
#include <type_traits>
#include <vector>
// U32
LL_COMMON_API LLSD ll_sd_from_U32(const U32);
@ -298,6 +304,11 @@ LLSD map(Ts&&... vs)
/*****************************************************************************
* LLSDParam
*****************************************************************************/
struct LLSDParamBase
{
virtual ~LLSDParamBase() {}
};
/**
* LLSDParam is a customization point for passing LLSD values to function
* parameters of more or less arbitrary type. LLSD provides a small set of
@ -315,7 +326,7 @@ LLSD map(Ts&&... vs)
* @endcode
*/
template <typename T>
class LLSDParam
class LLSDParam: public LLSDParamBase
{
public:
/**
@ -323,13 +334,66 @@ public:
* value for later retrieval
*/
LLSDParam(const LLSD& value):
_value(value)
value_(value)
{}
operator T() const { return _value; }
operator T() const { return value_; }
private:
T _value;
T value_;
};
/**
* LLSDParam<LLSD> is for when you don't already have the target parameter
* type in hand. Instantiate LLSDParam<LLSD>(your LLSD object), and the
* templated conversion operator will try to select a more specific LLSDParam
* specialization.
*/
template <>
class LLSDParam<LLSD>: public LLSDParamBase
{
private:
LLSD value_;
// LLSDParam<LLSD>::operator T() works by instantiating an LLSDParam<T> on
// demand. Returning that engages LLSDParam<T>::operator T(), producing
// the desired result. But LLSDParam<const char*> owns a std::string whose
// c_str() is returned by its operator const char*(). If we return a temp
// LLSDParam<const char*>, the compiler can destroy it right away, as soon
// as we've called operator const char*(). That's a problem! That
// invalidates the const char* we've just passed to the subject function.
// This LLSDParam<LLSD> is presumably guaranteed to survive until the
// subject function has returned, so we must ensure that any constructed
// LLSDParam<T> lives just as long as this LLSDParam<LLSD> does. Putting
// each LLSDParam<T> on the heap and capturing a smart pointer in a vector
// works. We would have liked to use std::unique_ptr, but vector entries
// must be copyable.
// (Alternatively we could assume that every instance of LLSDParam<LLSD>
// will be asked for at most ONE conversion. We could store a scalar
// std::unique_ptr and, when constructing an new LLSDParam<T>, assert that
// the unique_ptr is empty. But some future change in usage patterns, and
// consequent failure of that assertion, would be very mysterious. Instead
// of explaining how to fix it, just fix it now.)
mutable std::vector<std::shared_ptr<LLSDParamBase>> converters_;
public:
LLSDParam(const LLSD& value): value_(value) {}
/// if we're literally being asked for an LLSD parameter, avoid infinite
/// recursion
operator LLSD() const { return value_; }
/// otherwise, instantiate a more specific LLSDParam<T> to convert; that
/// preserves the existing customization mechanism
template <typename T>
operator T() const
{
// capture 'ptr' with the specific subclass type because converters_
// only stores LLSDParamBase pointers
auto ptr{ std::make_shared<LLSDParam<std::decay_t<T>>>(value_) };
// keep the new converter alive until we ourselves are destroyed
converters_.push_back(ptr);
return *ptr;
}
};
/**
@ -346,17 +410,17 @@ private:
*/
#define LLSDParam_for(T, AS) \
template <> \
class LLSDParam<T> \
class LLSDParam<T>: public LLSDParamBase \
{ \
public: \
LLSDParam(const LLSD& value): \
_value((T)value.AS()) \
value_((T)value.AS()) \
{} \
\
operator T() const { return _value; } \
operator T() const { return value_; } \
\
private: \
T _value; \
T value_; \
}
LLSDParam_for(float, asReal);
@ -372,31 +436,31 @@ LLSDParam_for(LLSD::Binary, asBinary);
* safely pass an LLSDParam<const char*>(yourLLSD).
*/
template <>
class LLSDParam<const char*>
class LLSDParam<const char*>: public LLSDParamBase
{
private:
// The difference here is that we store a std::string rather than a const
// char*. It's important that the LLSDParam object own the std::string.
std::string _value;
std::string value_;
// We don't bother storing the incoming LLSD object, but we do have to
// distinguish whether _value is an empty string because the LLSD object
// distinguish whether value_ is an empty string because the LLSD object
// contains an empty string or because it's isUndefined().
bool _undefined;
bool undefined_;
public:
LLSDParam(const LLSD& value):
_value(value),
_undefined(value.isUndefined())
value_(value),
undefined_(value.isUndefined())
{}
// The const char* we retrieve is for storage owned by our _value member.
// The const char* we retrieve is for storage owned by our value_ member.
// That's how we guarantee that the const char* is valid for the lifetime
// of this LLSDParam object. Constructing your LLSDParam in the argument
// list should ensure that the LLSDParam object will persist for the
// duration of the function call.
operator const char*() const
{
if (_undefined)
if (undefined_)
{
// By default, an isUndefined() LLSD object's asString() method
// will produce an empty string. But for a function accepting
@ -406,7 +470,7 @@ public:
// case, though, no LLSD value could pass NULL.
return NULL;
}
return _value.c_str();
return value_.c_str();
}
};
@ -555,4 +619,56 @@ struct hash<LLSD>
}
};
}
namespace LL
{
/*****************************************************************************
* apply(function, LLSD array)
*****************************************************************************/
// validate incoming LLSD blob, and return an LLSD array suitable to pass to
// the function of interest
LLSD apply_llsd_fix(size_t arity, const LLSD& args);
// Derived from https://stackoverflow.com/a/20441189
// and https://en.cppreference.com/w/cpp/utility/apply .
// We can't simply make a tuple from the LLSD array and then apply() that
// tuple to the function -- how would make_tuple() deduce the correct
// parameter type for each entry? We must go directly to the target function.
template <typename CALLABLE, std::size_t... I>
auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>)
{
// call func(unpacked args), using generic LLSDParam<LLSD> to convert each
// entry in 'array' to the target parameter type
return std::forward<CALLABLE>(func)(LLSDParam<LLSD>(array[I])...);
}
// use apply_n<ARITY>(function, LLSD) to call a specific arity of a variadic
// function with (that many) items from the passed LLSD array
template <size_t ARITY, typename CALLABLE>
auto apply_n(CALLABLE&& func, const LLSD& args)
{
return apply_impl(std::forward<CALLABLE>(func),
apply_llsd_fix(ARITY, args),
std::make_index_sequence<ARITY>());
}
/**
* apply(function, LLSD) goes beyond C++17 std::apply(). For this case
* @a function @emph cannot be variadic: the compiler must know at compile
* time how many arguments to pass. This isn't Python. (But see apply_n() to
* pass a specific number of args to a variadic function.)
*/
template <typename CALLABLE>
auto apply(CALLABLE&& func, const LLSD& args)
{
// infer arity from the definition of func
constexpr auto arity = function_arity<
typename std::remove_reference<CALLABLE>::type>::value;
// now that we have a compile-time arity, apply_n() works
return apply_n<arity>(std::forward<CALLABLE>(func), args);
}
} // namespace LL
#endif // LL_LLSDUTIL_H

View File

@ -771,20 +771,28 @@ static U32Kilobytes LLMemoryAdjustKBResult(U32Kilobytes inKB)
}
#endif
#if LL_DARWIN
// static
U32Kilobytes LLMemoryInfo::getHardwareMemSize()
{
// This might work on Linux as well. Someone check...
uint64_t phys = 0;
int mib[2] = { CTL_HW, HW_MEMSIZE };
size_t len = sizeof(phys);
sysctl(mib, 2, &phys, &len, NULL, 0);
return U64Bytes(phys);
}
#endif
U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const
{
#if LL_WINDOWS
return LLMemoryAdjustKBResult(U32Kilobytes(mStatsMap["Total Physical KB"].asInteger()));
#elif LL_DARWIN
// This might work on Linux as well. Someone check...
uint64_t phys = 0;
int mib[2] = { CTL_HW, HW_MEMSIZE };
size_t len = sizeof(phys);
sysctl(mib, 2, &phys, &len, NULL, 0);
return U64Bytes(phys);
return getHardwareMemSize();
#elif LL_LINUX
U64 phys = 0;

View File

@ -129,7 +129,10 @@ public:
LLMemoryInfo(); ///< Default constructor
void stream(std::ostream& s) const; ///< output text info to s
U32Kilobytes getPhysicalMemoryKB() const;
U32Kilobytes getPhysicalMemoryKB() const;
#if LL_DARWIN
static U32Kilobytes getHardwareMemSize(); // Because some Mac linkers won't let us reference extern gSysMemory from a different lib.
#endif
//get the available memory infomation in KiloBytes.
static void getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb);

View File

@ -42,6 +42,7 @@
#ifdef LL_WINDOWS
const DWORD MS_VC_EXCEPTION=0x406D1388;
#pragma pack(push,8)
@ -134,6 +135,15 @@ void LLThread::threadRun()
{
#ifdef LL_WINDOWS
set_thread_name(-1, mName.c_str());
#if 0 // probably a bad idea, see usage of SetThreadIdealProcessor in LLWindowWin32)
HANDLE hThread = GetCurrentThread();
if (hThread)
{
SetThreadAffinityMask(hThread, (DWORD_PTR) 0xFFFFFFFFFFFFFFFE);
}
#endif
#endif
LL_PROFILER_SET_THREAD_NAME( mName.c_str() );

View File

@ -30,6 +30,9 @@
#include "u64.h"
#include <chrono>
#include <thread>
#if LL_WINDOWS
# include "llwin32headerslean.h"
#elif LL_LINUX || LL_DARWIN
@ -62,9 +65,18 @@ LLTimer* LLTimer::sTimer = NULL;
//---------------------------------------------------------------------------
#if LL_WINDOWS
#if 0
void ms_sleep(U32 ms)
{
Sleep(ms);
LL_PROFILE_ZONE_SCOPED;
using TimePoint = std::chrono::steady_clock::time_point;
auto resume_time = TimePoint::clock::now() + std::chrono::milliseconds(ms);
while (TimePoint::clock::now() < resume_time)
{
std::this_thread::yield(); //note: don't use LLThread::yield here to avoid yielding for too long
}
}
U32 micro_sleep(U64 us, U32 max_yields)
@ -74,6 +86,35 @@ U32 micro_sleep(U64 us, U32 max_yields)
ms_sleep((U32)(us / 1000));
return 0;
}
#else
U32 micro_sleep(U64 us, U32 max_yields)
{
LL_PROFILE_ZONE_SCOPED
#if 0
LARGE_INTEGER ft;
ft.QuadPart = -static_cast<S64>(us * 10); // '-' using relative time
HANDLE timer = CreateWaitableTimer(NULL, TRUE, NULL);
SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0);
WaitForSingleObject(timer, INFINITE);
CloseHandle(timer);
#else
Sleep(us / 1000);
#endif
return 0;
}
void ms_sleep(U32 ms)
{
LL_PROFILE_ZONE_SCOPED
micro_sleep(ms * 1000, 0);
}
#endif
#elif LL_LINUX || LL_DARWIN
static void _sleep_loop(struct timespec& thiswait)
{

File diff suppressed because it is too large Load Diff

View File

@ -73,6 +73,7 @@ void LLWorkerThread::clearDeleteList()
{
worker->mRequestHandle = LLWorkerThread::nullHandle();
worker->clearFlags(LLWorkerClass::WCF_HAVE_WORK);
worker->clearFlags(LLWorkerClass::WCF_WORKING);
delete worker;
}
mDeleteList.clear() ;
@ -97,6 +98,7 @@ size_t LLWorkerThread::update(F32 max_time_ms)
{
if (worker->getFlags(LLWorkerClass::WCF_WORK_FINISHED))
{
worker->setFlags(LLWorkerClass::WCF_DELETE_REQUESTED);
delete_list.push_back(worker);
mDeleteList.erase(curiter);
}
@ -130,11 +132,11 @@ size_t LLWorkerThread::update(F32 max_time_ms)
//----------------------------------------------------------------------------
LLWorkerThread::handle_t LLWorkerThread::addWorkRequest(LLWorkerClass* workerclass, S32 param, U32 priority)
LLWorkerThread::handle_t LLWorkerThread::addWorkRequest(LLWorkerClass* workerclass, S32 param)
{
handle_t handle = generateHandle();
WorkRequest* req = new WorkRequest(handle, priority, workerclass, param);
WorkRequest* req = new WorkRequest(handle, workerclass, param);
bool res = addRequest(req);
if (!res)
@ -157,8 +159,8 @@ void LLWorkerThread::deleteWorker(LLWorkerClass* workerclass)
//============================================================================
// Runs on its OWN thread
LLWorkerThread::WorkRequest::WorkRequest(handle_t handle, U32 priority, LLWorkerClass* workerclass, S32 param) :
LLQueuedThread::QueuedRequest(handle, priority),
LLWorkerThread::WorkRequest::WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param) :
LLQueuedThread::QueuedRequest(handle),
mWorkerClass(workerclass),
mParam(param)
{
@ -177,6 +179,7 @@ void LLWorkerThread::WorkRequest::deleteRequest()
// virtual
bool LLWorkerThread::WorkRequest::processRequest()
{
LL_PROFILE_ZONE_SCOPED;
LLWorkerClass* workerclass = getWorkerClass();
workerclass->setWorking(true);
bool complete = workerclass->doWork(getParam());
@ -187,6 +190,7 @@ bool LLWorkerThread::WorkRequest::processRequest()
// virtual
void LLWorkerThread::WorkRequest::finishRequest(bool completed)
{
LL_PROFILE_ZONE_SCOPED;
LLWorkerClass* workerclass = getWorkerClass();
workerclass->finishWork(getParam(), completed);
U32 flags = LLWorkerClass::WCF_WORK_FINISHED | (completed ? 0 : LLWorkerClass::WCF_WORK_ABORTED);
@ -200,7 +204,6 @@ LLWorkerClass::LLWorkerClass(LLWorkerThread* workerthread, const std::string& na
: mWorkerThread(workerthread),
mWorkerClassName(name),
mRequestHandle(LLWorkerThread::nullHandle()),
mRequestPriority(LLWorkerThread::PRIORITY_NORMAL),
mMutex(),
mWorkFlags(0)
{
@ -289,7 +292,7 @@ bool LLWorkerClass::yield()
//----------------------------------------------------------------------------
// calls startWork, adds doWork() to queue
void LLWorkerClass::addWork(S32 param, U32 priority)
void LLWorkerClass::addWork(S32 param)
{
mMutex.lock();
llassert_always(!(mWorkFlags & (WCF_WORKING|WCF_HAVE_WORK)));
@ -303,7 +306,7 @@ void LLWorkerClass::addWork(S32 param, U32 priority)
startWork(param);
clearFlags(WCF_WORK_FINISHED|WCF_WORK_ABORTED);
setFlags(WCF_HAVE_WORK);
mRequestHandle = mWorkerThread->addWorkRequest(this, param, priority);
mRequestHandle = mWorkerThread->addWorkRequest(this, param);
mMutex.unlock();
}
@ -318,7 +321,6 @@ void LLWorkerClass::abortWork(bool autocomplete)
if (mRequestHandle != LLWorkerThread::nullHandle())
{
mWorkerThread->abortRequest(mRequestHandle, autocomplete);
mWorkerThread->setPriority(mRequestHandle, LLQueuedThread::PRIORITY_IMMEDIATE);
setFlags(WCF_ABORT_REQUESTED);
}
mMutex.unlock();
@ -392,16 +394,5 @@ void LLWorkerClass::scheduleDelete()
}
}
void LLWorkerClass::setPriority(U32 priority)
{
mMutex.lock();
if (mRequestHandle != LLWorkerThread::nullHandle() && mRequestPriority != priority)
{
mRequestPriority = priority;
mWorkerThread->setPriority(mRequestHandle, priority);
}
mMutex.unlock();
}
//============================================================================

View File

@ -56,7 +56,7 @@ public:
virtual ~WorkRequest(); // use deleteRequest()
public:
WorkRequest(handle_t handle, U32 priority, LLWorkerClass* workerclass, S32 param);
WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param);
S32 getParam()
{
@ -90,7 +90,7 @@ public:
/*virtual*/ size_t update(F32 max_time_ms);
handle_t addWorkRequest(LLWorkerClass* workerclass, S32 param, U32 priority = PRIORITY_NORMAL);
handle_t addWorkRequest(LLWorkerClass* workerclass, S32 param);
S32 getNumDeletes() { return (S32)mDeleteList.size(); } // debug
@ -151,10 +151,6 @@ public:
bool isWorking() { return getFlags(WCF_WORKING); }
bool wasAborted() { return getFlags(WCF_ABORT_REQUESTED); }
// setPriority(): changes the priority of a request
void setPriority(U32 priority);
U32 getPriority() { return mRequestPriority; }
const std::string& getName() const { return mWorkerClassName; }
protected:
@ -169,7 +165,7 @@ protected:
void setWorkerThread(LLWorkerThread* workerthread);
// addWork(): calls startWork, adds doWork() to queue
void addWork(S32 param, U32 priority = LLWorkerThread::PRIORITY_NORMAL);
void addWork(S32 param);
// abortWork(): requests that work be aborted
void abortWork(bool autocomplete);
@ -193,7 +189,6 @@ protected:
LLWorkerThread* mWorkerThread;
std::string mWorkerClassName;
handle_t mRequestHandle;
U32 mRequestPriority; // last priority set
private:
LLMutex mMutex;

View File

@ -0,0 +1,240 @@
/**
* @file apply_test.cpp
* @author Nat Goodspeed
* @date 2022-12-19
* @brief Test for apply.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
// Precompiled header
#include "linden_common.h"
// associated header
#include "apply.h"
// STL headers
// std headers
#include <iomanip>
// external library headers
// other Linden headers
#include "llsd.h"
#include "llsdutil.h"
#include <array>
#include <string>
#include <vector>
// for ensure_equals
std::ostream& operator<<(std::ostream& out, const std::vector<std::string>& stringvec)
{
const char* delim = "[";
for (const auto& str : stringvec)
{
out << delim << std::quoted(str);
delim = ", ";
}
return out << ']';
}
// the above must be declared BEFORE ensure_equals(std::vector<std::string>)
#include "../test/lltut.h"
/*****************************************************************************
* TUT
*****************************************************************************/
namespace tut
{
namespace statics
{
/*------------------------------ data ------------------------------*/
// Although we're using types from the LLSD namespace, we're not
// constructing LLSD values, but rather instances of the C++ types
// supported by LLSD.
static LLSD::Boolean b{true};
static LLSD::Integer i{17};
static LLSD::Real f{3.14};
static LLSD::String s{ "hello" };
static LLSD::UUID uu{ "baadf00d-dead-beef-baad-feedb0ef" };
static LLSD::Date dt{ "2022-12-19" };
static LLSD::URI uri{ "http://secondlife.com" };
static LLSD::Binary bin{ 0x01, 0x02, 0x03, 0x04, 0x05 };
static std::vector<LLSD::String> quick
{
"The", "quick", "brown", "fox", "etc."
};
static std::array<int, 5> fibs
{
0, 1, 1, 2, 3
};
// ensure that apply() actually reaches the target method --
// lack of ensure_equals() failure could be due to no-op apply()
bool called{ false };
// capture calls from collect()
std::vector<std::string> collected;
/*------------------------- test functions -------------------------*/
void various(LLSD::Boolean b, LLSD::Integer i, LLSD::Real f, const LLSD::String& s,
const LLSD::UUID& uu, const LLSD::Date& dt,
const LLSD::URI& uri, const LLSD::Binary& bin)
{
called = true;
ensure_equals( "b mismatch", b, statics::b);
ensure_equals( "i mismatch", i, statics::i);
ensure_equals( "f mismatch", f, statics::f);
ensure_equals( "s mismatch", s, statics::s);
ensure_equals( "uu mismatch", uu, statics::uu);
ensure_equals( "dt mismatch", dt, statics::dt);
ensure_equals("uri mismatch", uri, statics::uri);
ensure_equals("bin mismatch", bin, statics::bin);
}
void strings(std::string s0, std::string s1, std::string s2, std::string s3, std::string s4)
{
called = true;
ensure_equals("s0 mismatch", s0, statics::quick[0]);
ensure_equals("s1 mismatch", s1, statics::quick[1]);
ensure_equals("s2 mismatch", s2, statics::quick[2]);
ensure_equals("s3 mismatch", s3, statics::quick[3]);
ensure_equals("s4 mismatch", s4, statics::quick[4]);
}
void ints(int i0, int i1, int i2, int i3, int i4)
{
called = true;
ensure_equals("i0 mismatch", i0, statics::fibs[0]);
ensure_equals("i1 mismatch", i1, statics::fibs[1]);
ensure_equals("i2 mismatch", i2, statics::fibs[2]);
ensure_equals("i3 mismatch", i3, statics::fibs[3]);
ensure_equals("i4 mismatch", i4, statics::fibs[4]);
}
void sdfunc(const LLSD& sd)
{
called = true;
ensure_equals("sd mismatch", sd.asInteger(), statics::i);
}
void intfunc(int i)
{
called = true;
ensure_equals("i mismatch", i, statics::i);
}
void voidfunc()
{
called = true;
}
// recursion tail
void collect()
{
called = true;
}
// collect(arbitrary)
template <typename... ARGS>
void collect(const std::string& first, ARGS&&... rest)
{
statics::collected.push_back(first);
collect(std::forward<ARGS>(rest)...);
}
} // namespace statics
struct apply_data
{
apply_data()
{
// reset called before each test
statics::called = false;
statics::collected.clear();
}
};
typedef test_group<apply_data> apply_group;
typedef apply_group::object object;
apply_group applygrp("apply");
template<> template<>
void object::test<1>()
{
set_test_name("apply(tuple)");
LL::apply(statics::various,
std::make_tuple(statics::b, statics::i, statics::f, statics::s,
statics::uu, statics::dt, statics::uri, statics::bin));
ensure("apply(tuple) failed", statics::called);
}
template<> template<>
void object::test<2>()
{
set_test_name("apply(array)");
LL::apply(statics::ints, statics::fibs);
ensure("apply(array) failed", statics::called);
}
template<> template<>
void object::test<3>()
{
set_test_name("apply(vector)");
LL::apply(statics::strings, statics::quick);
ensure("apply(vector) failed", statics::called);
}
// The various apply(LLSD) tests exercise only the success cases because
// the failure cases trigger assert() fail, which is hard to catch.
template<> template<>
void object::test<4>()
{
set_test_name("apply(LLSD())");
LL::apply(statics::voidfunc, LLSD());
ensure("apply(LLSD()) failed", statics::called);
}
template<> template<>
void object::test<5>()
{
set_test_name("apply(fn(int), LLSD scalar)");
LL::apply(statics::intfunc, LLSD(statics::i));
ensure("apply(fn(int), LLSD scalar) failed", statics::called);
}
template<> template<>
void object::test<6>()
{
set_test_name("apply(fn(LLSD), LLSD scalar)");
// This test verifies that LLSDParam<LLSD> doesn't send the compiler
// into infinite recursion when the target is itself LLSD.
LL::apply(statics::sdfunc, LLSD(statics::i));
ensure("apply(fn(LLSD), LLSD scalar) failed", statics::called);
}
template<> template<>
void object::test<7>()
{
set_test_name("apply(LLSD array)");
LL::apply(statics::various,
llsd::array(statics::b, statics::i, statics::f, statics::s,
statics::uu, statics::dt, statics::uri, statics::bin));
ensure("apply(LLSD array) failed", statics::called);
}
template<> template<>
void object::test<8>()
{
set_test_name("VAPPLY()");
// Make a std::array<std::string> from statics::quick. We can't call a
// variadic function with a data structure of dynamic length.
std::array<std::string, 5> strray;
for (size_t i = 0; i < strray.size(); ++i)
strray[i] = statics::quick[i];
// This doesn't work: the compiler doesn't know which overload of
// collect() to pass to LL::apply().
// LL::apply(statics::collect, strray);
// That's what VAPPLY() is for.
VAPPLY(statics::collect, strray);
ensure("VAPPLY() failed", statics::called);
ensure_equals("collected mismatch", statics::collected, statics::quick);
}
} // namespace tut

View File

@ -0,0 +1,136 @@
/**
* @file lazyeventapi_test.cpp
* @author Nat Goodspeed
* @date 2022-06-18
* @brief Test for lazyeventapi.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
// Precompiled header
#include "linden_common.h"
// associated header
#include "lazyeventapi.h"
// STL headers
// std headers
// external library headers
// other Linden headers
#include "../test/lltut.h"
#include "llevents.h"
#include "llsdutil.h"
// observable side effect, solely for testing
static LLSD data;
// LLEventAPI listener subclass
class MyListener: public LLEventAPI
{
public:
// need this trivial forwarding constructor
// (of course do any other initialization your subclass requires)
MyListener(const LL::LazyEventAPIParams& params):
LLEventAPI(params)
{}
// example operation, registered by LazyEventAPI subclass below
void set_data(const LLSD& event)
{
data = event["data"];
}
};
// LazyEventAPI registrar subclass
class MyRegistrar: public LL::LazyEventAPI<MyListener>
{
using super = LL::LazyEventAPI<MyListener>;
using super::listener;
public:
// LazyEventAPI subclass initializes like a classic LLEventAPI subclass
// constructor, with API name and desc plus add() calls for the defined
// operations
MyRegistrar():
super("Test", "This is a test LLEventAPI")
{
add("set", "This is a set operation", &listener::set_data);
}
};
// Normally we'd declare a static instance of MyRegistrar -- but because we
// want to test both with and without, defer declaration to individual test
// methods.
/*****************************************************************************
* TUT
*****************************************************************************/
namespace tut
{
struct lazyeventapi_data
{
lazyeventapi_data()
{
// before every test, reset 'data'
data.clear();
}
~lazyeventapi_data()
{
// after every test, reset LLEventPumps
LLEventPumps::deleteSingleton();
}
};
typedef test_group<lazyeventapi_data> lazyeventapi_group;
typedef lazyeventapi_group::object object;
lazyeventapi_group lazyeventapigrp("lazyeventapi");
template<> template<>
void object::test<1>()
{
set_test_name("LazyEventAPI");
// this is where the magic (should) happen
// 'register' still a keyword until C++17
MyRegistrar regster;
LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "hey"));
ensure_equals("failed to set data", data.asString(), "hey");
}
template<> template<>
void object::test<2>()
{
set_test_name("No LazyEventAPI");
// Because the MyRegistrar declaration in test<1>() is local, because
// it has been destroyed, we fully expect NOT to reach a MyListener
// instance with this post.
LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "moot"));
ensure("accidentally set data", ! data.isDefined());
}
template<> template<>
void object::test<3>()
{
set_test_name("LazyEventAPI metadata");
MyRegistrar regster;
// Of course we have 'regster' in hand; we don't need to search for
// it. But this next test verifies that we can find (all) LazyEventAPI
// instances using LazyEventAPIBase::instance_snapshot. Normally we
// wouldn't search; normally we'd just look at each instance in the
// loop body.
const MyRegistrar* found = nullptr;
for (const auto& registrar : LL::LazyEventAPIBase::instance_snapshot())
if ((found = dynamic_cast<const MyRegistrar*>(&registrar)))
break;
ensure("Failed to find MyRegistrar via LLInstanceTracker", found);
ensure_equals("wrong API name", found->getName(), "Test");
ensure_contains("wrong API desc", found->getDesc(), "test LLEventAPI");
ensure_equals("wrong API field", found->getDispatchKey(), "op");
// Normally we'd just iterate over *found. But for test purposes,
// actually capture the range of NameDesc pairs in a vector.
std::vector<LL::LazyEventAPIBase::NameDesc> ops{ found->begin(), found->end() };
ensure_equals("failed to find operations", ops.size(), 1);
ensure_equals("wrong operation name", ops[0].first, "set");
ensure_contains("wrong operation desc", ops[0].second, "set operation");
LLSD metadata{ found->getMetadata(ops[0].first) };
ensure_equals("bad metadata name", metadata["name"].asString(), ops[0].first);
ensure_equals("bad metadata desc", metadata["desc"].asString(), ops[0].second);
}
} // namespace tut

View File

@ -18,9 +18,12 @@
// external library headers
// other Linden headers
#include "../test/lltut.h"
#include "lleventfilter.h"
#include "llsd.h"
#include "llsdutil.h"
#include "llevents.h"
#include "stringize.h"
#include "StringVec.h"
#include "tests/wrapllerrs.h"
#include "../test/catch_and_store_what_in.h"
#include "../test/debug.h"
@ -32,8 +35,6 @@
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/range.hpp>
#include <boost/foreach.hpp>
#define foreach BOOST_FOREACH
#include <boost/lambda/lambda.hpp>
@ -177,6 +178,7 @@ struct Vars
/*-------- Arbitrary-params (non-const, const, static) methods ---------*/
void methodna(NPARAMSa)
{
DEBUG;
// Because our const char* param cp might be NULL, and because we
// intend to capture the value in a std::string, have to distinguish
// between the NULL value and any non-NULL value. Use a convention
@ -188,7 +190,7 @@ struct Vars
else
vcp = std::string("'") + cp + "'";
debug()("methodna(", b,
this->debug()("methodna(", b,
", ", i,
", ", f,
", ", d,
@ -205,7 +207,7 @@ struct Vars
void methodnb(NPARAMSb)
{
std::ostringstream vbin;
foreach(U8 byte, bin)
for (U8 byte: bin)
{
vbin << std::hex << std::setfill('0') << std::setw(2) << unsigned(byte);
}
@ -226,7 +228,8 @@ struct Vars
void cmethodna(NPARAMSa) const
{
debug()('c', NONL);
DEBUG;
this->debug()('c', NONL);
const_cast<Vars*>(this)->methodna(NARGSa);
}
@ -315,6 +318,31 @@ void freenb(NPARAMSb)
*****************************************************************************/
namespace tut
{
void ensure_has(const std::string& outer, const std::string& inner)
{
ensure(stringize("'", outer, "' does not contain '", inner, "'"),
outer.find(inner) != std::string::npos);
}
template <typename CALLABLE>
std::string call_exc(CALLABLE&& func, const std::string& exc_frag)
{
std::string what =
catch_what<LLEventDispatcher::DispatchError>(std::forward<CALLABLE>(func));
ensure_has(what, exc_frag);
return what;
}
template <typename CALLABLE>
void call_logerr(CALLABLE&& func, const std::string& frag)
{
CaptureLog capture;
// the error should be logged; we just need to stop the exception
// propagating
catch_what<LLEventDispatcher::DispatchError>(std::forward<CALLABLE>(func));
capture.messageWith(frag);
}
struct lleventdispatcher_data
{
Debug debug{"test"};
@ -397,9 +425,9 @@ namespace tut
work.add(name, desc, &Dispatcher::cmethod1, required);
// Non-subclass method with/out required params
addf("method1", "method1", &v);
work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1));
work.add(name, desc, [this](const LLSD& args){ return v.method1(args); });
addf("method1_req", "method1", &v);
work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1), required);
work.add(name, desc, [this](const LLSD& args){ return v.method1(args); }, required);
/*--------------- Arbitrary params, array style ----------------*/
@ -461,7 +489,7 @@ namespace tut
debug("dft_array_full:\n",
dft_array_full);
// Partial defaults arrays.
foreach(LLSD::String a, ab)
for (LLSD::String a: ab)
{
LLSD::Integer partition(std::min(partial_offset, dft_array_full[a].size()));
dft_array_partial[a] =
@ -471,7 +499,7 @@ namespace tut
debug("dft_array_partial:\n",
dft_array_partial);
foreach(LLSD::String a, ab)
for(LLSD::String a: ab)
{
// Generate full defaults maps by zipping (params, dft_array_full).
dft_map_full[a] = zipmap(params[a], dft_array_full[a]);
@ -583,6 +611,7 @@ namespace tut
void addf(const std::string& n, const std::string& d, Vars* v)
{
debug("addf('", n, "', '", d, "')");
// This method is to capture in our own DescMap the name and
// description of every registered function, for metadata query
// testing.
@ -598,19 +627,14 @@ namespace tut
{
// Copy descs to a temp map of same type.
DescMap forgotten(descs.begin(), descs.end());
// LLEventDispatcher intentionally provides only const_iterator:
// since dereferencing that iterator generates values on the fly,
// it's meaningless to have a modifiable iterator. But since our
// 'work' object isn't const, by default BOOST_FOREACH() wants to
// use non-const iterators. Persuade it to use the const_iterator.
foreach(LLEventDispatcher::NameDesc nd, const_cast<const Dispatcher&>(work))
for (LLEventDispatcher::NameDesc nd: work)
{
DescMap::iterator found = forgotten.find(nd.first);
ensure(STRINGIZE("LLEventDispatcher records function '" << nd.first
<< "' we didn't enter"),
ensure(stringize("LLEventDispatcher records function '", nd.first,
"' we didn't enter"),
found != forgotten.end());
ensure_equals(STRINGIZE("LLEventDispatcher desc '" << nd.second <<
"' doesn't match what we entered: '" << found->second << "'"),
ensure_equals(stringize("LLEventDispatcher desc '", nd.second,
"' doesn't match what we entered: '", found->second, "'"),
nd.second, found->second);
// found in our map the name from LLEventDispatcher, good, erase
// our map entry
@ -621,41 +645,49 @@ namespace tut
std::ostringstream out;
out << "LLEventDispatcher failed to report";
const char* delim = ": ";
foreach(const DescMap::value_type& fme, forgotten)
for (const DescMap::value_type& fme: forgotten)
{
out << delim << fme.first;
delim = ", ";
}
ensure(out.str(), false);
throw failure(out.str());
}
}
Vars* varsfor(const std::string& name)
{
VarsMap::const_iterator found = funcvars.find(name);
ensure(STRINGIZE("No Vars* for " << name), found != funcvars.end());
ensure(STRINGIZE("NULL Vars* for " << name), found->second);
ensure(stringize("No Vars* for ", name), found != funcvars.end());
ensure(stringize("NULL Vars* for ", name), found->second);
return found->second;
}
void ensure_has(const std::string& outer, const std::string& inner)
std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
{
ensure(STRINGIZE("'" << outer << "' does not contain '" << inner << "'").c_str(),
outer.find(inner) != std::string::npos);
return tut::call_exc(
[this, func, args]()
{
if (func.empty())
{
work(args);
}
else
{
work(func, args);
}
},
exc_frag);
}
void call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
void call_logerr(const std::string& func, const LLSD& args, const std::string& frag)
{
std::string threw = catch_what<std::runtime_error>([this, &func, &args](){
work(func, args);
});
ensure_has(threw, exc_frag);
tut::call_logerr([this, func, args](){ work(func, args); }, frag);
}
LLSD getMetadata(const std::string& name)
{
LLSD meta(work.getMetadata(name));
ensure(STRINGIZE("No metadata for " << name), meta.isDefined());
ensure(stringize("No metadata for ", name), meta.isDefined());
return meta;
}
@ -724,7 +756,7 @@ namespace tut
set_test_name("map-style registration with non-array params");
// Pass "param names" as scalar or as map
LLSD attempts(llsd::array(17, LLSDMap("pi", 3.14)("two", 2)));
foreach(LLSD ae, inArray(attempts))
for (LLSD ae: inArray(attempts))
{
std::string threw = catch_what<std::exception>([this, &ae](){
work.add("freena_err", "freena", freena, ae);
@ -799,7 +831,7 @@ namespace tut
{
set_test_name("query Callables with/out required params");
LLSD names(llsd::array("free1", "Dmethod1", "Dcmethod1", "method1"));
foreach(LLSD nm, inArray(names))
for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
@ -828,19 +860,19 @@ namespace tut
(5, llsd::array("freena_array", "smethodna_array", "methodna_array")),
llsd::array
(5, llsd::array("freenb_array", "smethodnb_array", "methodnb_array"))));
foreach(LLSD ae, inArray(expected))
for (LLSD ae: inArray(expected))
{
LLSD::Integer arity(ae[0].asInteger());
LLSD names(ae[1]);
LLSD req(LLSD::emptyArray());
if (arity)
req[arity - 1] = LLSD();
foreach(LLSD nm, inArray(names))
for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
ensure_equals(metadata["desc"].asString(), descs[nm]);
ensure_equals(STRINGIZE("mismatched required for " << nm.asString()),
ensure_equals(stringize("mismatched required for ", nm.asString()),
metadata["required"], req);
ensure("should not have optional", metadata["optional"].isUndefined());
}
@ -854,7 +886,7 @@ namespace tut
// - (Free function | non-static method), map style, no params (ergo
// no defaults)
LLSD names(llsd::array("free0_map", "smethod0_map", "method0_map"));
foreach(LLSD nm, inArray(names))
for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
@ -884,7 +916,7 @@ namespace tut
llsd::array("smethodnb_map_adft", "smethodnb_map_mdft"),
llsd::array("methodna_map_adft", "methodna_map_mdft"),
llsd::array("methodnb_map_adft", "methodnb_map_mdft")));
foreach(LLSD eq, inArray(equivalences))
for (LLSD eq: inArray(equivalences))
{
LLSD adft(eq[0]);
LLSD mdft(eq[1]);
@ -898,8 +930,8 @@ namespace tut
ensure_equals("mdft name", mdft, mmeta["name"]);
ameta.erase("name");
mmeta.erase("name");
ensure_equals(STRINGIZE("metadata for " << adft.asString()
<< " vs. " << mdft.asString()),
ensure_equals(stringize("metadata for ", adft.asString(),
" vs. ", mdft.asString()),
ameta, mmeta);
}
}
@ -915,7 +947,7 @@ namespace tut
// params are required. Also maps containing left requirements for
// partial defaults arrays. Also defaults maps from defaults arrays.
LLSD allreq, leftreq, rightdft;
foreach(LLSD::String a, ab)
for (LLSD::String a: ab)
{
// The map in which all params are required uses params[a] as
// keys, with all isUndefined() as values. We can accomplish that
@ -943,9 +975,9 @@ namespace tut
// Generate maps containing parameter names not provided by the
// dft_map_partial maps.
LLSD skipreq(allreq);
foreach(LLSD::String a, ab)
for (LLSD::String a: ab)
{
foreach(const MapEntry& me, inMap(dft_map_partial[a]))
for (const MapEntry& me: inMap(dft_map_partial[a]))
{
skipreq[a].erase(me.first);
}
@ -990,7 +1022,7 @@ namespace tut
(llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"),
llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional
foreach(LLSD grp, inArray(groups))
for (LLSD grp: inArray(groups))
{
// Internal structure of each group in 'groups':
LLSD names(grp[0]);
@ -1003,14 +1035,14 @@ namespace tut
optional);
// Loop through 'names'
foreach(LLSD nm, inArray(names))
for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
ensure_equals(nm.asString(), metadata["desc"].asString(), descs[nm]);
ensure_equals(STRINGIZE(nm << " required mismatch"),
ensure_equals(stringize(nm, " required mismatch"),
metadata["required"], required);
ensure_equals(STRINGIZE(nm << " optional mismatch"),
ensure_equals(stringize(nm, " optional mismatch"),
metadata["optional"], optional);
}
}
@ -1031,13 +1063,7 @@ namespace tut
{
set_test_name("call with bad name");
call_exc("freek", LLSD(), "not found");
// We don't have a comparable helper function for the one-arg
// operator() method, and it's not worth building one just for this
// case. Write it out.
std::string threw = catch_what<std::runtime_error>([this](){
work(LLSDMap("op", "freek"));
});
ensure_has(threw, "bad");
std::string threw = call_exc("", LLSDMap("op", "freek"), "bad");
ensure_has(threw, "op");
ensure_has(threw, "freek");
}
@ -1079,7 +1105,7 @@ namespace tut
// LLSD value matching 'required' according to llsd_matches() rules.
LLSD matching(LLSDMap("d", 3.14)("array", llsd::array("answer", true, answer)));
// Okay, walk through 'tests'.
foreach(const CallablesTriple& tr, tests)
for (const CallablesTriple& tr: tests)
{
// Should be able to pass 'answer' to Callables registered
// without 'required'.
@ -1087,7 +1113,7 @@ namespace tut
ensure_equals("answer mismatch", tr.llsd, answer);
// Should NOT be able to pass 'answer' to Callables registered
// with 'required'.
call_exc(tr.name_req, answer, "bad request");
call_logerr(tr.name_req, answer, "bad request");
// But SHOULD be able to pass 'matching' to Callables registered
// with 'required'.
work(tr.name_req, matching);
@ -1101,17 +1127,20 @@ namespace tut
set_test_name("passing wrong args to (map | array)-style registrations");
// Pass scalar/map to array-style functions, scalar/array to map-style
// functions. As that validation happens well before we engage the
// argument magic, it seems pointless to repeat this with every
// variation: (free function | non-static method), (no | arbitrary)
// args. We should only need to engage it for one map-style
// registration and one array-style registration.
std::string array_exc("needs an args array");
call_exc("free0_array", 17, array_exc);
call_exc("free0_array", LLSDMap("pi", 3.14), array_exc);
// functions. It seems pointless to repeat this with every variation:
// (free function | non-static method), (no | arbitrary) args. We
// should only need to engage it for one map-style registration and
// one array-style registration.
// Now that LLEventDispatcher has been extended to treat an LLSD
// scalar as a single-entry array, the error we expect in this case is
// that apply() is trying to pass that non-empty array to a nullary
// function.
call_logerr("free0_array", 17, "LL::apply");
// similarly, apply() doesn't accept an LLSD Map
call_logerr("free0_array", LLSDMap("pi", 3.14), "unsupported");
std::string map_exc("needs a map");
call_exc("free0_map", 17, map_exc);
call_logerr("free0_map", 17, map_exc);
// Passing an array to a map-style function works now! No longer an
// error case!
// call_exc("free0_map", llsd::array("a", "b"), map_exc);
@ -1125,7 +1154,7 @@ namespace tut
("free0_array", "free0_map",
"smethod0_array", "smethod0_map",
"method0_array", "method0_map"));
foreach(LLSD name, inArray(names))
for (LLSD name: inArray(names))
{
// Look up the Vars instance for this function.
Vars* vars(varsfor(name));
@ -1150,15 +1179,21 @@ namespace tut
template<> template<>
void object::test<19>()
{
set_test_name("call array-style functions with too-short arrays");
// Could have two different too-short arrays, one for *na and one for
// *nb, but since they both take 5 params...
set_test_name("call array-style functions with wrong-length arrays");
// Could have different wrong-length arrays for *na and for *nb, but
// since they both take 5 params...
LLSD tooshort(llsd::array("this", "array", "too", "short"));
foreach(const LLSD& funcsab, inArray(array_funcs))
LLSD toolong (llsd::array("this", "array", "is", "one", "too", "long"));
LLSD badargs (llsd::array(tooshort, toolong));
for (const LLSD& toosomething: inArray(badargs))
{
foreach(const llsd::MapEntry& e, inMap(funcsab))
for (const LLSD& funcsab: inArray(array_funcs))
{
call_exc(e.second, tooshort, "requires more arguments");
for (const llsd::MapEntry& e: inMap(funcsab))
{
// apply() complains about wrong number of array entries
call_logerr(e.second, toosomething, "LL::apply");
}
}
}
}
@ -1166,7 +1201,7 @@ namespace tut
template<> template<>
void object::test<20>()
{
set_test_name("call array-style functions with (just right | too long) arrays");
set_test_name("call array-style functions with right-size arrays");
std::vector<U8> binary;
for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i)
{
@ -1178,40 +1213,25 @@ namespace tut
LLDate("2011-02-03T15:07:00Z"),
LLURI("http://secondlife.com"),
binary)));
LLSD argsplus(args);
argsplus["a"].append("bogus");
argsplus["b"].append("bogus");
LLSD expect;
foreach(LLSD::String a, ab)
for (LLSD::String a: ab)
{
expect[a] = zipmap(params[a], args[a]);
}
// Adjust expect["a"]["cp"] for special Vars::cp treatment.
expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'";
expect["a"]["cp"] = stringize("'", expect["a"]["cp"].asString(), "'");
debug("expect: ", expect);
// Use substantially the same logic for args and argsplus
LLSD argsarrays(llsd::array(args, argsplus));
// So i==0 selects 'args', i==1 selects argsplus
for (LLSD::Integer i(0), iend(argsarrays.size()); i < iend; ++i)
for (const LLSD& funcsab: inArray(array_funcs))
{
foreach(const LLSD& funcsab, inArray(array_funcs))
for (LLSD::String a: ab)
{
foreach(LLSD::String a, ab)
{
// Reset the Vars instance before each call
Vars* vars(varsfor(funcsab[a]));
*vars = Vars();
work(funcsab[a], argsarrays[i][a]);
ensure_llsd(STRINGIZE(funcsab[a].asString() <<
": expect[\"" << a << "\"] mismatch"),
vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits
// TODO: in the i==1 or argsplus case, intercept LL_WARNS
// output? Even without that, using argsplus verifies that
// passing too many args isn't fatal; it works -- but
// would be nice to notice the warning too.
}
// Reset the Vars instance before each call
Vars* vars(varsfor(funcsab[a]));
*vars = Vars();
work(funcsab[a], args[a]);
ensure_llsd(stringize(funcsab[a].asString(), ": expect[\"", a, "\"] mismatch"),
vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits
}
}
}
@ -1239,7 +1259,7 @@ namespace tut
("a", llsd::array(false, 255, 98.6, 1024.5, "pointer"))
("b", llsd::array("object", LLUUID::generateNewID(), LLDate::now(), LLURI("http://wiki.lindenlab.com/wiki"), LLSD::Binary(boost::begin(binary), boost::end(binary)))));
LLSD array_overfull(array_full);
foreach(LLSD::String a, ab)
for (LLSD::String a: ab)
{
array_overfull[a].append("bogus");
}
@ -1253,7 +1273,7 @@ namespace tut
ensure_not_equals("UUID collision",
array_full["b"][1].asUUID(), dft_array_full["b"][1].asUUID());
LLSD map_full, map_overfull;
foreach(LLSD::String a, ab)
for (LLSD::String a: ab)
{
map_full[a] = zipmap(params[a], array_full[a]);
map_overfull[a] = map_full[a];
@ -1294,21 +1314,360 @@ namespace tut
"freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft")));
// Treat (full | overfull) (array | map) the same.
LLSD argssets(llsd::array(array_full, array_overfull, map_full, map_overfull));
foreach(const LLSD& args, inArray(argssets))
for (const LLSD& args: inArray(argssets))
{
foreach(LLSD::String a, ab)
for (LLSD::String a: ab)
{
foreach(LLSD::String name, inArray(names[a]))
for (LLSD::String name: inArray(names[a]))
{
// Reset the Vars instance
Vars* vars(varsfor(name));
*vars = Vars();
work(name, args[a]);
ensure_llsd(STRINGIZE(name << ": expect[\"" << a << "\"] mismatch"),
ensure_llsd(stringize(name, ": expect[\"", a, "\"] mismatch"),
vars->inspect(), expect[a], 7); // 7 bits, 2 decimal digits
// intercept LL_WARNS for the two overfull cases?
}
}
}
}
struct DispatchResult: public LLDispatchListener
{
using DR = DispatchResult;
DispatchResult(): LLDispatchListener("results", "op")
{
add("strfunc", "return string", &DR::strfunc);
add("voidfunc", "void function", &DR::voidfunc);
add("emptyfunc", "return empty LLSD", &DR::emptyfunc);
add("intfunc", "return Integer LLSD", &DR::intfunc);
add("llsdfunc", "return passed LLSD", &DR::llsdfunc);
add("mapfunc", "return map LLSD", &DR::mapfunc);
add("arrayfunc", "return array LLSD", &DR::arrayfunc);
}
std::string strfunc(const std::string& str) const { return "got " + str; }
void voidfunc() const {}
LLSD emptyfunc() const { return {}; }
int intfunc(int i) const { return -i; }
LLSD llsdfunc(const LLSD& event) const
{
LLSD result{ event };
result["with"] = "string";
return result;
}
LLSD mapfunc(int i, const std::string& str) const
{
return llsd::map("i", intfunc(i), "str", strfunc(str));
}
LLSD arrayfunc(int i, const std::string& str) const
{
return llsd::array(intfunc(i), strfunc(str));
}
};
template<> template<>
void object::test<23>()
{
set_test_name("string result");
DispatchResult service;
LLSD result{ service("strfunc", "a string") };
ensure_equals("strfunc() mismatch", result.asString(), "got a string");
}
template<> template<>
void object::test<24>()
{
set_test_name("void result");
DispatchResult service;
LLSD result{ service("voidfunc", LLSD()) };
ensure("voidfunc() returned defined", result.isUndefined());
}
template<> template<>
void object::test<25>()
{
set_test_name("Integer result");
DispatchResult service;
LLSD result{ service("intfunc", -17) };
ensure_equals("intfunc() mismatch", result.asInteger(), 17);
}
template<> template<>
void object::test<26>()
{
set_test_name("LLSD echo");
DispatchResult service;
LLSD result{ service("llsdfunc", llsd::map("op", "llsdfunc", "reqid", 17)) };
ensure_equals("llsdfunc() mismatch", result,
llsd::map("op", "llsdfunc", "reqid", 17, "with", "string"));
}
template<> template<>
void object::test<27>()
{
set_test_name("map LLSD result");
DispatchResult service;
LLSD result{ service("mapfunc", llsd::array(-12, "value")) };
ensure_equals("mapfunc() mismatch", result, llsd::map("i", 12, "str", "got value"));
}
template<> template<>
void object::test<28>()
{
set_test_name("array LLSD result");
DispatchResult service;
LLSD result{ service("arrayfunc", llsd::array(-8, "word")) };
ensure_equals("arrayfunc() mismatch", result, llsd::array(8, "got word"));
}
template<> template<>
void object::test<29>()
{
set_test_name("listener error, no reply");
DispatchResult service;
tut::call_exc(
[&service]()
{ service.post(llsd::map("op", "nosuchfunc", "reqid", 17)); },
"nosuchfunc");
}
template<> template<>
void object::test<30>()
{
set_test_name("listener error with reply");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map("op", "nosuchfunc", "reqid", 17, "reply", result.getName()));
LLSD reply{ result.get() };
ensure("no reply", reply.isDefined());
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
ensure_has(reply["error"].asString(), "nosuchfunc");
}
template<> template<>
void object::test<31>()
{
set_test_name("listener call to void function");
DispatchResult service;
LLCaptureListener<LLSD> result;
result.set("non-empty");
for (const auto& func: StringVec{ "voidfunc", "emptyfunc" })
{
service.post(llsd::map(
"op", func,
"reqid", 17,
"reply", result.getName()));
ensure_equals("reply from " + func, result.get().asString(), "non-empty");
}
}
template<> template<>
void object::test<32>()
{
set_test_name("listener call to string function");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", "strfunc",
"args", llsd::array("a string"),
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
ensure_equals("bad reply from strfunc", reply["data"].asString(), "got a string");
}
template<> template<>
void object::test<33>()
{
set_test_name("listener call to map function");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", "mapfunc",
"args", llsd::array(-7, "value"),
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
ensure_equals("bad i from mapfunc", reply["i"].asInteger(), 7);
ensure_equals("bad str from mapfunc", reply["str"], "got value");
}
template<> template<>
void object::test<34>()
{
set_test_name("batched map success");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", llsd::map(
"strfunc", "some string",
"intfunc", 2,
"voidfunc", LLSD(),
"arrayfunc", llsd::array(-5, "other string")),
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
reply.erase("reqid");
ensure_equals(
"bad map batch",
reply,
llsd::map(
"strfunc", "got some string",
"intfunc", -2,
"voidfunc", LLSD(),
"arrayfunc", llsd::array(5, "got other string")));
}
template<> template<>
void object::test<35>()
{
set_test_name("batched map error");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", llsd::map(
"badfunc", 34, // !
"strfunc", "some string",
"intfunc", 2,
"missing", LLSD(), // !
"voidfunc", LLSD(),
"arrayfunc", llsd::array(-5, "other string")),
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
reply.erase("reqid");
auto error{ reply["error"].asString() };
reply.erase("error");
ensure_has(error, "badfunc");
ensure_has(error, "missing");
ensure_equals(
"bad partial batch",
reply,
llsd::map(
"strfunc", "got some string",
"intfunc", -2,
"voidfunc", LLSD(),
"arrayfunc", llsd::array(5, "got other string")));
}
template<> template<>
void object::test<36>()
{
set_test_name("batched map exception");
DispatchResult service;
auto error = tut::call_exc(
[&service]()
{
service.post(llsd::map(
"op", llsd::map(
"badfunc", 34, // !
"strfunc", "some string",
"intfunc", 2,
"missing", LLSD(), // !
"voidfunc", LLSD(),
"arrayfunc", llsd::array(-5, "other string")),
"reqid", 17));
// no "reply"
},
"badfunc");
ensure_has(error, "missing");
}
template<> template<>
void object::test<37>()
{
set_test_name("batched array success");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", llsd::array(
llsd::array("strfunc", "some string"),
llsd::array("intfunc", 2),
"arrayfunc",
"voidfunc"),
"args", llsd::array(
LLSD(),
LLSD(),
llsd::array(-5, "other string")),
// args array deliberately short, since the default
// [3] is undefined, which should work for voidfunc
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
reply.erase("reqid");
ensure_equals(
"bad array batch",
reply,
llsd::map(
"data", llsd::array(
"got some string",
-2,
llsd::array(5, "got other string"),
LLSD())));
}
template<> template<>
void object::test<38>()
{
set_test_name("batched array error");
DispatchResult service;
LLCaptureListener<LLSD> result;
service.post(llsd::map(
"op", llsd::array(
llsd::array("strfunc", "some string"),
llsd::array("intfunc", 2, "whoops"), // bad form
"arrayfunc",
"voidfunc"),
"args", llsd::array(
LLSD(),
LLSD(),
llsd::array(-5, "other string")),
// args array deliberately short, since the default
// [3] is undefined, which should work for voidfunc
"reqid", 17,
"reply", result.getName()));
LLSD reply{ result.get() };
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
reply.erase("reqid");
auto error{ reply["error"] };
reply.erase("error");
ensure_has(error, "[1]");
ensure_has(error, "unsupported");
ensure_equals("bad array batch", reply,
llsd::map("data", llsd::array("got some string")));
}
template<> template<>
void object::test<39>()
{
set_test_name("batched array exception");
DispatchResult service;
auto error = tut::call_exc(
[&service]()
{
service.post(llsd::map(
"op", llsd::array(
llsd::array("strfunc", "some string"),
llsd::array("intfunc", 2, "whoops"), // bad form
"arrayfunc",
"voidfunc"),
"args", llsd::array(
LLSD(),
LLSD(),
llsd::array(-5, "other string")),
// args array deliberately short, since the default
// [3] is undefined, which should work for voidfunc
"reqid", 17));
// no "reply"
},
"[1]");
ensure_has(error, "unsupported");
}
} // namespace tut

View File

@ -38,7 +38,7 @@ namespace tut
{
struct workqueue_data
{
WorkQueue queue{"queue"};
WorkSchedule queue{"queue"};
};
typedef test_group<workqueue_data> workqueue_group;
typedef workqueue_group::object object;
@ -49,8 +49,8 @@ namespace tut
{
set_test_name("name");
ensure_equals("didn't capture name", queue.getKey(), "queue");
ensure("not findable", WorkQueue::getInstance("queue") == queue.getWeak().lock());
WorkQueue q2;
ensure("not findable", WorkSchedule::getInstance("queue") == queue.getWeak().lock());
WorkSchedule q2;
ensure("has no name", LLStringUtil::startsWith(q2.getKey(), "WorkQueue"));
}
@ -73,16 +73,16 @@ namespace tut
{
set_test_name("postEvery");
// record of runs
using Shared = std::deque<WorkQueue::TimePoint>;
using Shared = std::deque<WorkSchedule::TimePoint>;
// This is an example of how to share data between the originator of
// postEvery(work) and the work item itself, since usually a WorkQueue
// postEvery(work) and the work item itself, since usually a WorkSchedule
// is used to dispatch work to a different thread. Neither of them
// should call any of LLCond's wait methods: you don't want to stall
// either the worker thread or the originating thread (conventionally
// main). Use LLCond or a subclass even if all you want to do is
// signal the work item that it can quit; consider LLOneShotCond.
LLCond<Shared> data;
auto start = WorkQueue::TimePoint::clock::now();
auto start = WorkSchedule::TimePoint::clock::now();
// 2s seems like a long time to wait, since it directly impacts the
// duration of this test program. Unfortunately GitHub's Mac runners
// are pretty wimpy, and we're getting spurious "too late" errors just
@ -97,7 +97,7 @@ namespace tut
data.update_one(
[](Shared& data)
{
data.push_back(WorkQueue::TimePoint::clock::now());
data.push_back(WorkSchedule::TimePoint::clock::now());
});
// by the 3rd call, return false to stop
return (++count < 3);
@ -106,7 +106,7 @@ namespace tut
// postEvery() running, so run until we have exhausted the iterations
// or we time out waiting
for (auto finish = start + 10*interval;
WorkQueue::TimePoint::clock::now() < finish &&
WorkSchedule::TimePoint::clock::now() < finish &&
data.get([](const Shared& data){ return data.size(); }) < 3; )
{
queue.runPending();
@ -143,8 +143,8 @@ namespace tut
void object::test<4>()
{
set_test_name("postTo");
WorkQueue main("main");
auto qptr = WorkQueue::getInstance("queue");
WorkSchedule main("main");
auto qptr = WorkSchedule::getInstance("queue");
int result = 0;
main.postTo(
qptr,
@ -175,8 +175,8 @@ namespace tut
void object::test<5>()
{
set_test_name("postTo with void return");
WorkQueue main("main");
auto qptr = WorkQueue::getInstance("queue");
WorkSchedule main("main");
auto qptr = WorkSchedule::getInstance("queue");
std::string observe;
main.postTo(
qptr,
@ -198,7 +198,7 @@ namespace tut
std::string stored;
// Try to call waitForResult() on this thread's main coroutine. It
// should throw because the main coroutine must service the queue.
auto what{ catch_what<WorkQueue::Error>(
auto what{ catch_what<WorkSchedule::Error>(
[this, &stored](){ stored = queue.waitForResult(
[](){ return "should throw"; }); }) };
ensure("lambda should not have run", stored.empty());

View File

@ -226,6 +226,11 @@ public:
return boost::dynamic_pointer_cast<CaptureLogRecorder>(mRecorder)->streamto(out);
}
friend inline std::ostream& operator<<(std::ostream& out, const CaptureLog& self)
{
return self.streamto(out);
}
private:
LLError::FatalFunction mFatalFunction;
LLError::SettingsStoragePtr mOldSettings;

View File

@ -17,18 +17,58 @@
// std headers
// external library headers
// other Linden headers
#include "commoncontrol.h"
#include "llerror.h"
#include "llevents.h"
#include "llsd.h"
#include "stringize.h"
LL::ThreadPool::ThreadPool(const std::string& name, size_t threads, size_t capacity):
#include <boost/fiber/algo/round_robin.hpp>
/*****************************************************************************
* Custom fiber scheduler for worker threads
*****************************************************************************/
// As of 2022-12-06, each of our worker threads only runs a single (default)
// fiber: we don't launch explicit fibers within worker threads, nor do we
// anticipate doing so. So a worker thread that's simply waiting for incoming
// tasks should really sleep a little. Override the default fiber scheduler to
// implement that.
struct sleepy_robin: public boost::fibers::algo::round_robin
{
virtual void suspend_until( std::chrono::steady_clock::time_point const&) noexcept
{
#if LL_WINDOWS
// round_robin holds a std::condition_variable, and
// round_robin::suspend_until() calls
// std::condition_variable::wait_until(). On Windows, that call seems
// busier than it ought to be. Try just sleeping.
Sleep(1);
#else
// currently unused other than windows, but might as well have something here
// different units than Sleep(), but we actually just want to sleep for any de-minimis duration
usleep(1);
#endif
}
virtual void notify() noexcept
{
// Since our Sleep() call above will wake up on its own, we need not
// take any special action to wake it.
}
};
/*****************************************************************************
* ThreadPoolBase
*****************************************************************************/
LL::ThreadPoolBase::ThreadPoolBase(const std::string& name, size_t threads,
WorkQueueBase* queue):
super(name),
mQueue(name, capacity),
mName("ThreadPool:" + name),
mThreadCount(threads)
mThreadCount(getConfiguredWidth(name, threads)),
mQueue(queue)
{}
void LL::ThreadPool::start()
void LL::ThreadPoolBase::start()
{
for (size_t i = 0; i < mThreadCount; ++i)
{
@ -56,17 +96,17 @@ void LL::ThreadPool::start()
});
}
LL::ThreadPool::~ThreadPool()
LL::ThreadPoolBase::~ThreadPoolBase()
{
close();
}
void LL::ThreadPool::close()
void LL::ThreadPoolBase::close()
{
if (! mQueue.isClosed())
if (! mQueue->isClosed())
{
LL_DEBUGS("ThreadPool") << mName << " closing queue and joining threads" << LL_ENDL;
mQueue.close();
mQueue->close();
for (auto& pair: mThreads)
{
LL_DEBUGS("ThreadPool") << mName << " waiting on thread " << pair.first << LL_ENDL;
@ -76,14 +116,74 @@ void LL::ThreadPool::close()
}
}
void LL::ThreadPool::run(const std::string& name)
void LL::ThreadPoolBase::run(const std::string& name)
{
#if LL_WINDOWS
// Try using sleepy_robin fiber scheduler.
boost::fibers::use_scheduling_algorithm<sleepy_robin>();
#endif // LL_WINDOWS
LL_DEBUGS("ThreadPool") << name << " starting" << LL_ENDL;
run();
LL_DEBUGS("ThreadPool") << name << " stopping" << LL_ENDL;
}
void LL::ThreadPool::run()
void LL::ThreadPoolBase::run()
{
mQueue.runUntilClose();
mQueue->runUntilClose();
}
//static
size_t LL::ThreadPoolBase::getConfiguredWidth(const std::string& name, size_t dft)
{
LLSD poolSizes;
try
{
poolSizes = LL::CommonControl::get("Global", "ThreadPoolSizes");
// "ThreadPoolSizes" is actually a map containing the sizes of
// interest -- or should be, if this process has an
// LLViewerControlListener instance and its settings include
// "ThreadPoolSizes". If we failed to retrieve it, perhaps we're in a
// program that doesn't define that, or perhaps there's no such
// setting, or perhaps we're asking too early, before the LLEventAPI
// itself has been instantiated. In any of those cases, it seems worth
// warning.
if (! poolSizes.isDefined())
{
// Note: we don't warn about absence of an override key for a
// particular ThreadPool name, that's fine. This warning is about
// complete absence of a ThreadPoolSizes setting, which we expect
// in a normal viewer session.
LL_WARNS("ThreadPool") << "No 'ThreadPoolSizes' setting for ThreadPool '"
<< name << "'" << LL_ENDL;
}
}
catch (const LL::CommonControl::Error& exc)
{
// We don't want ThreadPool to *require* LLViewerControlListener.
// Just log it and carry on.
LL_WARNS("ThreadPool") << "Can't check 'ThreadPoolSizes': " << exc.what() << LL_ENDL;
}
LL_DEBUGS("ThreadPool") << "ThreadPoolSizes = " << poolSizes << LL_ENDL;
// LLSD treats an undefined value as an empty map when asked to retrieve a
// key, so we don't need this to be conditional.
LLSD sizeSpec{ poolSizes[name] };
// We retrieve sizeSpec as LLSD, rather than immediately as LLSD::Integer,
// so we can distinguish the case when it's undefined.
return sizeSpec.isInteger() ? sizeSpec.asInteger() : dft;
}
//static
size_t LL::ThreadPoolBase::getWidth(const std::string& name, size_t dft)
{
auto instance{ getInstance(name) };
if (instance)
{
return instance->getWidth();
}
else
{
return getConfiguredWidth(name, dft);
}
}

View File

@ -13,7 +13,9 @@
#if ! defined(LL_THREADPOOL_H)
#define LL_THREADPOOL_H
#include "threadpool_fwd.h"
#include "workqueue.h"
#include <memory> // std::unique_ptr
#include <string>
#include <thread>
#include <utility> // std::pair
@ -22,17 +24,24 @@
namespace LL
{
class ThreadPool: public LLInstanceTracker<ThreadPool, std::string>
class ThreadPoolBase: public LLInstanceTracker<ThreadPoolBase, std::string>
{
private:
using super = LLInstanceTracker<ThreadPool, std::string>;
using super = LLInstanceTracker<ThreadPoolBase, std::string>;
public:
/**
* Pass ThreadPool a string name. This can be used to look up the
* Pass ThreadPoolBase a string name. This can be used to look up the
* relevant WorkQueue.
*
* The number of threads you pass sets the compile-time default. But
* if the user has overridden the LLSD map in the "ThreadPoolSizes"
* setting with a key matching this ThreadPool name, that setting
* overrides this parameter.
*/
ThreadPool(const std::string& name, size_t threads=1, size_t capacity=1024);
virtual ~ThreadPool();
ThreadPoolBase(const std::string& name, size_t threads,
WorkQueueBase* queue);
virtual ~ThreadPoolBase();
/**
* Launch the ThreadPool. Until this call, a constructed ThreadPool
@ -50,8 +59,6 @@ namespace LL
std::string getName() const { return mName; }
size_t getWidth() const { return mThreads.size(); }
/// obtain a non-const reference to the WorkQueue to post work to it
WorkQueue& getQueue() { return mQueue; }
/**
* Override run() if you need special processing. The default run()
@ -59,15 +66,72 @@ namespace LL
*/
virtual void run();
/**
* getConfiguredWidth() returns the setting, if any, for the specified
* ThreadPool name. Returns dft if the "ThreadPoolSizes" map does not
* contain the specified name.
*/
static
size_t getConfiguredWidth(const std::string& name, size_t dft=0);
/**
* This getWidth() returns the width of the instantiated ThreadPool
* with the specified name, if any. If no instance exists, returns its
* getConfiguredWidth() if any. If there's no instance and no relevant
* override, return dft. Presumably dft should match the threads
* parameter passed to the ThreadPool constructor call that will
* eventually instantiate the ThreadPool with that name.
*/
static
size_t getWidth(const std::string& name, size_t dft);
protected:
std::unique_ptr<WorkQueueBase> mQueue;
private:
void run(const std::string& name);
WorkQueue mQueue;
std::string mName;
size_t mThreadCount;
std::vector<std::pair<std::string, std::thread>> mThreads;
};
/**
* Specialize with WorkQueue or, for timestamped tasks, WorkSchedule
*/
template <class QUEUE>
struct ThreadPoolUsing: public ThreadPoolBase
{
using queue_t = QUEUE;
/**
* Pass ThreadPoolUsing a string name. This can be used to look up the
* relevant WorkQueue.
*
* The number of threads you pass sets the compile-time default. But
* if the user has overridden the LLSD map in the "ThreadPoolSizes"
* setting with a key matching this ThreadPool name, that setting
* overrides this parameter.
*
* Pass an explicit capacity to limit the size of the queue.
* Constraining the queue can cause a submitter to block. Do not
* constrain any ThreadPool accepting work from the main thread.
*/
ThreadPoolUsing(const std::string& name, size_t threads=1, size_t capacity=1024*1024):
ThreadPoolBase(name, threads, new queue_t(name, capacity))
{}
~ThreadPoolUsing() override {}
/**
* obtain a non-const reference to the specific WorkQueue subclass to
* post work to it
*/
queue_t& getQueue() { return static_cast<queue_t&>(*mQueue); }
};
/// ThreadPool is shorthand for using the simpler WorkQueue
using ThreadPool = ThreadPoolUsing<WorkQueue>;
} // namespace LL
#endif /* ! defined(LL_THREADPOOL_H) */

View File

@ -0,0 +1,25 @@
/**
* @file threadpool_fwd.h
* @author Nat Goodspeed
* @date 2022-12-09
* @brief Forward declarations for ThreadPool et al.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
* Copyright (c) 2022, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_THREADPOOL_FWD_H)
#define LL_THREADPOOL_FWD_H
#include "workqueue.h"
namespace LL
{
template <class QUEUE>
struct ThreadPoolUsing;
using ThreadPool = ThreadPoolUsing<WorkQueue>;
} // namespace LL
#endif /* ! defined(LL_THREADPOOL_FWD_H) */

View File

@ -26,14 +26,121 @@
using Mutex = LLCoros::Mutex;
using Lock = LLCoros::LockType;
LL::WorkQueue::WorkQueue(const std::string& name, size_t capacity):
super(makeName(name)),
mQueue(capacity)
/*****************************************************************************
* WorkQueueBase
*****************************************************************************/
LL::WorkQueueBase::WorkQueueBase(const std::string& name):
super(makeName(name))
{
// TODO: register for "LLApp" events so we can implicitly close() on
// viewer shutdown.
}
void LL::WorkQueueBase::runUntilClose()
{
try
{
for (;;)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
callWork(pop_());
}
}
catch (const Closed&)
{
}
}
bool LL::WorkQueueBase::runPending()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
for (Work work; tryPop_(work); )
{
callWork(work);
}
return ! done();
}
bool LL::WorkQueueBase::runOne()
{
Work work;
if (tryPop_(work))
{
callWork(work);
}
return ! done();
}
bool LL::WorkQueueBase::runUntil(const TimePoint& until)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
// Should we subtract some slop to allow for typical Work execution time?
// How much slop?
// runUntil() is simply a time-bounded runPending().
for (Work work; TimePoint::clock::now() < until && tryPop_(work); )
{
callWork(work);
}
return ! done();
}
std::string LL::WorkQueueBase::makeName(const std::string& name)
{
if (! name.empty())
return name;
static U32 discriminator = 0;
static Mutex mutex;
U32 num;
{
// Protect discriminator from concurrent access by different threads.
// It can't be thread_local, else two racing threads will come up with
// the same name.
Lock lk(mutex);
num = discriminator++;
}
return STRINGIZE("WorkQueue" << num);
}
void LL::WorkQueueBase::callWork(const Work& work)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
try
{
work();
}
catch (...)
{
// No matter what goes wrong with any individual work item, the worker
// thread must go on! Log our own instance name with the exception.
LOG_UNHANDLED_EXCEPTION(getKey());
}
}
void LL::WorkQueueBase::error(const std::string& msg)
{
LL_ERRS("WorkQueue") << msg << LL_ENDL;
}
void LL::WorkQueueBase::checkCoroutine(const std::string& method)
{
// By convention, the default coroutine on each thread has an empty name
// string. See also LLCoros::logname().
if (LLCoros::getName().empty())
{
LLTHROW(Error("Do not call " + method + " from a thread's default coroutine"));
}
}
/*****************************************************************************
* WorkQueue
*****************************************************************************/
LL::WorkQueue::WorkQueue(const std::string& name, size_t capacity):
super(name),
mQueue(capacity)
{
}
void LL::WorkQueue::close()
{
mQueue.close();
@ -54,105 +161,85 @@ bool LL::WorkQueue::done()
return mQueue.done();
}
void LL::WorkQueue::runUntilClose()
bool LL::WorkQueue::post(const Work& callable)
{
try
{
for (;;)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
callWork(mQueue.pop());
}
}
catch (const Queue::Closed&)
{
}
return mQueue.pushIfOpen(callable);
}
bool LL::WorkQueue::runPending()
bool LL::WorkQueue::tryPost(const Work& callable)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
for (Work work; mQueue.tryPop(work); )
{
callWork(work);
}
return ! mQueue.done();
return mQueue.tryPush(callable);
}
bool LL::WorkQueue::runOne()
LL::WorkQueue::Work LL::WorkQueue::pop_()
{
Work work;
if (mQueue.tryPop(work))
{
callWork(work);
}
return ! mQueue.done();
return mQueue.pop();
}
bool LL::WorkQueue::runUntil(const TimePoint& until)
bool LL::WorkQueue::tryPop_(Work& work)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
// Should we subtract some slop to allow for typical Work execution time?
// How much slop?
// runUntil() is simply a time-bounded runPending().
for (Work work; TimePoint::clock::now() < until && mQueue.tryPop(work); )
{
callWork(work);
}
return ! mQueue.done();
return mQueue.tryPop(work);
}
std::string LL::WorkQueue::makeName(const std::string& name)
/*****************************************************************************
* WorkSchedule
*****************************************************************************/
LL::WorkSchedule::WorkSchedule(const std::string& name, size_t capacity):
super(name),
mQueue(capacity)
{
if (! name.empty())
return name;
static U32 discriminator = 0;
static Mutex mutex;
U32 num;
{
// Protect discriminator from concurrent access by different threads.
// It can't be thread_local, else two racing threads will come up with
// the same name.
Lock lk(mutex);
num = discriminator++;
}
return STRINGIZE("WorkQueue" << num);
}
void LL::WorkQueue::callWork(const Queue::DataTuple& work)
void LL::WorkSchedule::close()
{
// ThreadSafeSchedule::pop() always delivers a tuple, even when
// there's only one data field per item, as for us.
callWork(std::get<0>(work));
mQueue.close();
}
void LL::WorkQueue::callWork(const Work& work)
size_t LL::WorkSchedule::size()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
try
{
work();
}
catch (...)
{
// No matter what goes wrong with any individual work item, the worker
// thread must go on! Log our own instance name with the exception.
LOG_UNHANDLED_EXCEPTION(getKey());
}
return mQueue.size();
}
void LL::WorkQueue::error(const std::string& msg)
bool LL::WorkSchedule::isClosed()
{
LL_ERRS("WorkQueue") << msg << LL_ENDL;
return mQueue.isClosed();
}
void LL::WorkQueue::checkCoroutine(const std::string& method)
bool LL::WorkSchedule::done()
{
// By convention, the default coroutine on each thread has an empty name
// string. See also LLCoros::logname().
if (LLCoros::getName().empty())
{
LLTHROW(Error("Do not call " + method + " from a thread's default coroutine"));
}
return mQueue.done();
}
bool LL::WorkSchedule::post(const Work& callable)
{
// Use TimePoint::clock::now() instead of TimePoint's representation of
// the epoch because this WorkSchedule may contain a mix of past-due
// TimedWork items and TimedWork items scheduled for the future. Sift this
// new item into the correct place.
return post(callable, TimePoint::clock::now());
}
bool LL::WorkSchedule::post(const Work& callable, const TimePoint& time)
{
return mQueue.pushIfOpen(TimedWork(time, callable));
}
bool LL::WorkSchedule::tryPost(const Work& callable)
{
return tryPost(callable, TimePoint::clock::now());
}
bool LL::WorkSchedule::tryPost(const Work& callable, const TimePoint& time)
{
return mQueue.tryPush(TimedWork(time, callable));
}
LL::WorkSchedule::Work LL::WorkSchedule::pop_()
{
return std::get<0>(mQueue.pop());
}
bool LL::WorkSchedule::tryPop_(Work& work)
{
return mQueue.tryPop(work);
}

View File

@ -15,6 +15,7 @@
#include "llcoros.h"
#include "llexception.h"
#include "llinstancetracker.h"
#include "llinstancetrackersubclass.h"
#include "threadsafeschedule.h"
#include <chrono>
#include <exception> // std::current_exception
@ -23,27 +24,23 @@
namespace LL
{
/*****************************************************************************
* WorkQueueBase: API for WorkQueue and WorkSchedule
*****************************************************************************/
/**
* A typical WorkQueue has a string name that can be used to find it.
*/
class WorkQueue: public LLInstanceTracker<WorkQueue, std::string>
class WorkQueueBase: public LLInstanceTracker<WorkQueueBase, std::string>
{
private:
using super = LLInstanceTracker<WorkQueue, std::string>;
using super = LLInstanceTracker<WorkQueueBase, std::string>;
public:
using Work = std::function<void()>;
private:
using Queue = ThreadSafeSchedule<Work>;
// helper for postEvery()
template <typename Rep, typename Period, typename CALLABLE>
class BackJack;
public:
using TimePoint = Queue::TimePoint;
using TimedWork = Queue::TimeTuple;
using Closed = Queue::Closed;
using Closed = LLThreadSafeQueueInterrupt;
// for runFor()
using TimePoint = std::chrono::steady_clock::time_point;
struct Error: public LLException
{
@ -51,18 +48,18 @@ namespace LL
};
/**
* You may omit the WorkQueue name, in which case a unique name is
* You may omit the WorkQueueBase name, in which case a unique name is
* synthesized; for practical purposes that makes it anonymous.
*/
WorkQueue(const std::string& name = std::string(), size_t capacity=1024);
WorkQueueBase(const std::string& name);
/**
* Since the point of WorkQueue is to pass work to some other worker
* thread(s) asynchronously, it's important that the WorkQueue continue
* to exist until the worker thread(s) have drained it. To communicate
* that it's time for them to quit, close() the queue.
* thread(s) asynchronously, it's important that it continue to exist
* until the worker thread(s) have drained it. To communicate that
* it's time for them to quit, close() the queue.
*/
void close();
virtual void close() = 0;
/**
* WorkQueue supports multiple producers and multiple consumers. In
@ -78,152 +75,57 @@ namespace LL
* * If you're the only consumer, noticing that size() > 0 is
* meaningful.
*/
size_t size();
virtual size_t size() = 0;
/// producer end: are we prevented from pushing any additional items?
bool isClosed();
virtual bool isClosed() = 0;
/// consumer end: are we done, is the queue entirely drained?
bool done();
virtual bool done() = 0;
/*---------------------- fire and forget API -----------------------*/
/// fire-and-forget, but at a particular (future?) time
template <typename CALLABLE>
void post(const TimePoint& time, CALLABLE&& callable)
{
// Defer reifying an arbitrary CALLABLE until we hit this or
// postIfOpen(). All other methods should accept CALLABLEs of
// arbitrary type to avoid multiple levels of std::function
// indirection.
mQueue.push(TimedWork(time, std::move(callable)));
}
/// fire-and-forget
template <typename CALLABLE>
void post(CALLABLE&& callable)
{
// We use TimePoint::clock::now() instead of TimePoint's
// representation of the epoch because this WorkQueue may contain
// a mix of past-due TimedWork items and TimedWork items scheduled
// for the future. Sift this new item into the correct place.
post(TimePoint::clock::now(), std::move(callable));
}
/**
* post work for a particular time, unless the queue is closed before
* we can post
*/
template <typename CALLABLE>
bool postIfOpen(const TimePoint& time, CALLABLE&& callable)
{
// Defer reifying an arbitrary CALLABLE until we hit this or
// post(). All other methods should accept CALLABLEs of arbitrary
// type to avoid multiple levels of std::function indirection.
return mQueue.pushIfOpen(TimedWork(time, std::move(callable)));
}
/**
* post work, unless the queue is closed before we can post
*/
template <typename CALLABLE>
bool postIfOpen(CALLABLE&& callable)
{
return postIfOpen(TimePoint::clock::now(), std::move(callable));
}
virtual bool post(const Work&) = 0;
/**
* Post work to be run at a specified time to another WorkQueue, which
* may or may not still exist and be open. Return true if we were able
* to post.
* post work, unless the queue is full
*/
template <typename CALLABLE>
static bool postMaybe(weak_t target, const TimePoint& time, CALLABLE&& callable);
virtual bool tryPost(const Work&) = 0;
/**
* Post work to another WorkQueue, which may or may not still exist
* and be open. Return true if we were able to post.
* and be open. Support any post() overload. Return true if we were
* able to post.
*/
template <typename CALLABLE>
static bool postMaybe(weak_t target, CALLABLE&& callable)
{
return postMaybe(target, TimePoint::clock::now(),
std::forward<CALLABLE>(callable));
}
/**
* Launch a callable returning bool that will trigger repeatedly at
* specified interval, until the callable returns false.
*
* If you need to signal that callable from outside, DO NOT bind a
* reference to a simple bool! That's not thread-safe. Instead, bind
* an LLCond variant, e.g. LLOneShotCond or LLBoolCond.
*/
template <typename Rep, typename Period, typename CALLABLE>
void postEvery(const std::chrono::duration<Rep, Period>& interval,
CALLABLE&& callable);
template <typename CALLABLE>
bool tryPost(CALLABLE&& callable)
{
return mQueue.tryPush(TimedWork(TimePoint::clock::now(), std::move(callable)));
}
template <typename... ARGS>
static bool postMaybe(weak_t target, ARGS&&... args);
/*------------------------- handshake API --------------------------*/
/**
* Post work to another WorkQueue to be run at a specified time,
* requesting a specific callback to be run on this WorkQueue on
* completion.
*
* Returns true if able to post, false if the other WorkQueue is
* inaccessible.
*/
// Apparently some Microsoft header file defines a macro CALLBACK? The
// natural template argument name CALLBACK produces very weird Visual
// Studio compile errors that seem utterly unrelated to this source
// code.
template <typename CALLABLE, typename FOLLOWUP>
bool postTo(weak_t target,
const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback);
/**
* Post work to another WorkQueue, requesting a specific callback to
* be run on this WorkQueue on completion.
* be run on this WorkQueue on completion. Optional final argument is
* TimePoint for WorkSchedule.
*
* Returns true if able to post, false if the other WorkQueue is
* inaccessible.
*/
template <typename CALLABLE, typename FOLLOWUP>
bool postTo(weak_t target, CALLABLE&& callable, FOLLOWUP&& callback)
{
return postTo(target, TimePoint::clock::now(),
std::move(callable), std::move(callback));
}
/**
* Post work to another WorkQueue to be run at a specified time,
* blocking the calling coroutine until then, returning the result to
* caller on completion.
*
* In general, we assume that each thread's default coroutine is busy
* servicing its WorkQueue or whatever. To try to prevent mistakes, we
* forbid calling waitForResult() from a thread's default coroutine.
*/
template <typename CALLABLE>
auto waitForResult(const TimePoint& time, CALLABLE&& callable);
template <typename CALLABLE, typename FOLLOWUP, typename... ARGS>
bool postTo(weak_t target, CALLABLE&& callable, FOLLOWUP&& callback,
ARGS&&... args);
/**
* Post work to another WorkQueue, blocking the calling coroutine
* until then, returning the result to caller on completion.
* until then, returning the result to caller on completion. Optional
* final argument is TimePoint for WorkSchedule.
*
* In general, we assume that each thread's default coroutine is busy
* servicing its WorkQueue or whatever. To try to prevent mistakes, we
* forbid calling waitForResult() from a thread's default coroutine.
*/
template <typename CALLABLE>
auto waitForResult(CALLABLE&& callable)
{
return waitForResult(TimePoint::clock::now(), std::move(callable));
}
template <typename CALLABLE, typename... ARGS>
auto waitForResult(CALLABLE&& callable, ARGS&&... args);
/*--------------------------- worker API ---------------------------*/
@ -270,7 +172,7 @@ namespace LL
*/
bool runUntil(const TimePoint& until);
private:
protected:
template <typename CALLABLE, typename FOLLOWUP>
static auto makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback);
/// general case: arbitrary C++ return type
@ -290,13 +192,170 @@ namespace LL
static void checkCoroutine(const std::string& method);
static void error(const std::string& msg);
static std::string makeName(const std::string& name);
void callWork(const Queue::DataTuple& work);
void callWork(const Work& work);
private:
virtual Work pop_() = 0;
virtual bool tryPop_(Work&) = 0;
};
/*****************************************************************************
* WorkQueue: no timestamped task support
*****************************************************************************/
class WorkQueue: public LLInstanceTrackerSubclass<WorkQueue, WorkQueueBase>
{
private:
using super = LLInstanceTrackerSubclass<WorkQueue, WorkQueueBase>;
public:
/**
* You may omit the WorkQueue name, in which case a unique name is
* synthesized; for practical purposes that makes it anonymous.
*/
WorkQueue(const std::string& name = std::string(), size_t capacity=1024);
/**
* Since the point of WorkQueue is to pass work to some other worker
* thread(s) asynchronously, it's important that it continue to exist
* until the worker thread(s) have drained it. To communicate that
* it's time for them to quit, close() the queue.
*/
void close() override;
/**
* WorkQueue supports multiple producers and multiple consumers. In
* the general case it's misleading to test size(), since any other
* thread might change it the nanosecond the lock is released. On that
* basis, some might argue against publishing a size() method at all.
*
* But there are two specific cases in which a test based on size()
* might be reasonable:
*
* * If you're the only producer, noticing that size() == 0 is
* meaningful.
* * If you're the only consumer, noticing that size() > 0 is
* meaningful.
*/
size_t size() override;
/// producer end: are we prevented from pushing any additional items?
bool isClosed() override;
/// consumer end: are we done, is the queue entirely drained?
bool done() override;
/*---------------------- fire and forget API -----------------------*/
/**
* post work, unless the queue is closed before we can post
*/
bool post(const Work&) override;
/**
* post work, unless the queue is full
*/
bool tryPost(const Work&) override;
private:
using Queue = LLThreadSafeQueue<Work>;
Queue mQueue;
Work pop_() override;
bool tryPop_(Work&) override;
};
/*****************************************************************************
* WorkSchedule: add support for timestamped tasks
*****************************************************************************/
class WorkSchedule: public LLInstanceTrackerSubclass<WorkSchedule, WorkQueueBase>
{
private:
using super = LLInstanceTrackerSubclass<WorkSchedule, WorkQueueBase>;
using Queue = ThreadSafeSchedule<Work>;
// helper for postEvery()
template <typename Rep, typename Period, typename CALLABLE>
class BackJack;
public:
using TimePoint = Queue::TimePoint;
using TimedWork = Queue::TimeTuple;
/**
* You may omit the WorkSchedule name, in which case a unique name is
* synthesized; for practical purposes that makes it anonymous.
*/
WorkSchedule(const std::string& name = std::string(), size_t capacity=1024);
/**
* Since the point of WorkSchedule is to pass work to some other worker
* thread(s) asynchronously, it's important that the WorkSchedule continue
* to exist until the worker thread(s) have drained it. To communicate
* that it's time for them to quit, close() the queue.
*/
void close() override;
/**
* WorkSchedule supports multiple producers and multiple consumers. In
* the general case it's misleading to test size(), since any other
* thread might change it the nanosecond the lock is released. On that
* basis, some might argue against publishing a size() method at all.
*
* But there are two specific cases in which a test based on size()
* might be reasonable:
*
* * If you're the only producer, noticing that size() == 0 is
* meaningful.
* * If you're the only consumer, noticing that size() > 0 is
* meaningful.
*/
size_t size() override;
/// producer end: are we prevented from pushing any additional items?
bool isClosed() override;
/// consumer end: are we done, is the queue entirely drained?
bool done() override;
/*---------------------- fire and forget API -----------------------*/
/**
* post work, unless the queue is closed before we can post
*/
bool post(const Work& callable) override;
/**
* post work for a particular time, unless the queue is closed before
* we can post
*/
bool post(const Work& callable, const TimePoint& time);
/**
* post work, unless the queue is full
*/
bool tryPost(const Work& callable) override;
/**
* post work for a particular time, unless the queue is full
*/
bool tryPost(const Work& callable, const TimePoint& time);
/**
* Launch a callable returning bool that will trigger repeatedly at
* specified interval, until the callable returns false.
*
* If you need to signal that callable from outside, DO NOT bind a
* reference to a simple bool! That's not thread-safe. Instead, bind
* an LLCond variant, e.g. LLOneShotCond or LLBoolCond.
*/
template <typename Rep, typename Period, typename CALLABLE>
bool postEvery(const std::chrono::duration<Rep, Period>& interval,
CALLABLE&& callable);
private:
Queue mQueue;
Work pop_() override;
bool tryPop_(Work&) override;
};
/**
* BackJack is, in effect, a hand-rolled lambda, binding a WorkQueue, a
* BackJack is, in effect, a hand-rolled lambda, binding a WorkSchedule, a
* CALLABLE that returns bool, a TimePoint and an interval at which to
* relaunch it. As long as the callable continues returning true, BackJack
* keeps resubmitting it to the target WorkQueue.
@ -305,7 +364,7 @@ namespace LL
// class method gets its own 'this' pointer -- which we need to resubmit
// the whole BackJack callable.
template <typename Rep, typename Period, typename CALLABLE>
class WorkQueue::BackJack
class WorkSchedule::BackJack
{
public:
// bind the desired data
@ -319,9 +378,10 @@ namespace LL
mCallable(std::move(callable))
{}
// Call by target WorkQueue -- note that although WE require a
// callable returning bool, WorkQueue wants a void callable. We
// consume the bool.
// This operator() method, called by target WorkSchedule, is what
// makes this object a Work item. Although WE require a callable
// returning bool, WorkSchedule wants a void callable. We consume the
// bool.
void operator()()
{
// If mCallable() throws an exception, don't catch it here: if it
@ -337,7 +397,7 @@ namespace LL
// register our intent to fire at exact mIntervals.
mStart += mInterval;
// We're being called at this moment by the target WorkQueue.
// We're being called at this moment by the target WorkSchedule.
// Assume it still exists, rather than checking the result of
// lock().
// Resubmit the whole *this callable: that's why we're a class
@ -345,14 +405,10 @@ namespace LL
// move-only callable; but naturally this statement must be
// the last time we reference this instance, which may become
// moved-from.
try
{
mTarget.lock()->post(mStart, std::move(*this));
}
catch (const Closed&)
{
// Once this queue is closed, oh well, just stop
}
auto target{ std::dynamic_pointer_cast<WorkSchedule>(mTarget.lock()) };
// Discard bool return: once this queue is closed, oh well,
// just stop
target->post(std::move(*this), mStart);
}
}
@ -364,8 +420,8 @@ namespace LL
};
template <typename Rep, typename Period, typename CALLABLE>
void WorkQueue::postEvery(const std::chrono::duration<Rep, Period>& interval,
CALLABLE&& callable)
bool WorkSchedule::postEvery(const std::chrono::duration<Rep, Period>& interval,
CALLABLE&& callable)
{
if (interval.count() <= 0)
{
@ -381,14 +437,14 @@ namespace LL
// Instantiate and post a suitable BackJack, binding a weak_ptr to
// self, the current time, the desired interval and the desired
// callable.
post(
return post(
BackJack<Rep, Period, CALLABLE>(
getWeak(), TimePoint::clock::now(), interval, std::move(callable)));
}
/// general case: arbitrary C++ return type
template <typename CALLABLE, typename FOLLOWUP, typename RETURNTYPE>
struct WorkQueue::MakeReplyLambda
struct WorkQueueBase::MakeReplyLambda
{
auto operator()(CALLABLE&& callable, FOLLOWUP&& callback)
{
@ -409,7 +465,7 @@ namespace LL
/// specialize for CALLABLE returning void
template <typename CALLABLE, typename FOLLOWUP>
struct WorkQueue::MakeReplyLambda<CALLABLE, FOLLOWUP, void>
struct WorkQueueBase::MakeReplyLambda<CALLABLE, FOLLOWUP, void>
{
auto operator()(CALLABLE&& callable, FOLLOWUP&& callback)
{
@ -421,16 +477,16 @@ namespace LL
};
template <typename CALLABLE, typename FOLLOWUP>
auto WorkQueue::makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback)
auto WorkQueueBase::makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback)
{
return MakeReplyLambda<CALLABLE, FOLLOWUP,
decltype(std::forward<CALLABLE>(callable)())>()
(std::move(callable), std::move(callback));
}
template <typename CALLABLE, typename FOLLOWUP>
bool WorkQueue::postTo(weak_t target,
const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback)
template <typename CALLABLE, typename FOLLOWUP, typename... ARGS>
bool WorkQueueBase::postTo(weak_t target, CALLABLE&& callable, FOLLOWUP&& callback,
ARGS&&... args)
{
LL_PROFILE_ZONE_SCOPED;
// We're being asked to post to the WorkQueue at target.
@ -443,13 +499,12 @@ namespace LL
// Here we believe target WorkQueue still exists. Post to it a
// lambda that packages our callable, our callback and a weak_ptr
// to this originating WorkQueue.
tptr->post(
time,
return tptr->post(
[reply = super::getWeak(),
callable = std::move(callable),
callback = std::move(callback)]
()
mutable {
() mutable
{
// Use postMaybe() below in case this originating WorkQueue
// has been closed or destroyed. Remember, the outer lambda is
// now running on a thread servicing the target WorkQueue, and
@ -472,44 +527,34 @@ namespace LL
// originating WorkQueue. Once there, rethrow it.
[exc = std::current_exception()](){ std::rethrow_exception(exc); });
}
});
// looks like we were able to post()
return true;
},
// if caller passed a TimePoint, pass it along to post()
std::forward<ARGS>(args)...);
}
template <typename CALLABLE>
bool WorkQueue::postMaybe(weak_t target, const TimePoint& time, CALLABLE&& callable)
template <typename... ARGS>
bool WorkQueueBase::postMaybe(weak_t target, ARGS&&... args)
{
LL_PROFILE_ZONE_SCOPED;
// target is a weak_ptr: have to lock it to check it
auto tptr = target.lock();
if (tptr)
{
try
{
tptr->post(time, std::forward<CALLABLE>(callable));
// we were able to post()
return true;
}
catch (const Closed&)
{
// target WorkQueue still exists, but is Closed
}
return tptr->post(std::forward<ARGS>(args)...);
}
// either target no longer exists, or its WorkQueue is Closed
// target no longer exists
return false;
}
/// general case: arbitrary C++ return type
template <typename CALLABLE, typename RETURNTYPE>
struct WorkQueue::WaitForResult
struct WorkQueueBase::WaitForResult
{
auto operator()(WorkQueue* self, const TimePoint& time, CALLABLE&& callable)
template <typename... ARGS>
auto operator()(WorkQueueBase* self, CALLABLE&& callable, ARGS&&... args)
{
LLCoros::Promise<RETURNTYPE> promise;
self->post(
time,
bool posted = self->post(
// We dare to bind a reference to Promise because it's
// specifically designed for cross-thread communication.
[&promise, callable = std::move(callable)]()
@ -523,7 +568,13 @@ namespace LL
{
promise.set_exception(std::current_exception());
}
});
},
// if caller passed a TimePoint, pass it to post()
std::forward<ARGS>(args)...);
if (! posted)
{
LLTHROW(WorkQueueBase::Closed());
}
auto future{ LLCoros::getFuture(promise) };
// now, on the calling thread, wait for that result
LLCoros::TempStatus st("waiting for WorkQueue::waitForResult()");
@ -533,13 +584,13 @@ namespace LL
/// specialize for CALLABLE returning void
template <typename CALLABLE>
struct WorkQueue::WaitForResult<CALLABLE, void>
struct WorkQueueBase::WaitForResult<CALLABLE, void>
{
void operator()(WorkQueue* self, const TimePoint& time, CALLABLE&& callable)
template <typename... ARGS>
void operator()(WorkQueueBase* self, CALLABLE&& callable, ARGS&&... args)
{
LLCoros::Promise<void> promise;
self->post(
time,
bool posted = self->post(
// &promise is designed for cross-thread access
[&promise, callable = std::move(callable)]()
mutable {
@ -552,7 +603,13 @@ namespace LL
{
promise.set_exception(std::current_exception());
}
});
},
// if caller passed a TimePoint, pass it to post()
std::forward<ARGS>(args)...);
if (! posted)
{
LLTHROW(WorkQueueBase::Closed());
}
auto future{ LLCoros::getFuture(promise) };
// block until set_value()
LLCoros::TempStatus st("waiting for void WorkQueue::waitForResult()");
@ -560,13 +617,13 @@ namespace LL
}
};
template <typename CALLABLE>
auto WorkQueue::waitForResult(const TimePoint& time, CALLABLE&& callable)
template <typename CALLABLE, typename... ARGS>
auto WorkQueueBase::waitForResult(CALLABLE&& callable, ARGS&&... args)
{
checkCoroutine("waitForResult()");
// derive callable's return type so we can specialize for void
return WaitForResult<CALLABLE, decltype(std::forward<CALLABLE>(callable)())>()
(this, time, std::forward<CALLABLE>(callable));
(this, std::forward<CALLABLE>(callable), std::forward<ARGS>(args)...);
}
} // namespace LL

View File

@ -113,6 +113,7 @@ void HttpLibcurl::shutdown()
void HttpLibcurl::start(int policy_count)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
llassert_always(policy_count <= HTTP_POLICY_CLASS_LIMIT);
llassert_always(! mMultiHandles); // One-time call only
@ -143,6 +144,7 @@ void HttpLibcurl::start(int policy_count)
// sleep otherwise ask for a normal polling interval.
HttpService::ELoopSpeed HttpLibcurl::processTransport()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpService::ELoopSpeed ret(HttpService::REQUEST_SLEEP);
// Give libcurl some cycles to do I/O & callbacks
@ -168,6 +170,7 @@ HttpService::ELoopSpeed HttpLibcurl::processTransport()
CURLMcode status(CURLM_CALL_MULTI_PERFORM);
do
{
LL_PROFILE_ZONE_NAMED_CATEGORY_NETWORK("httppt - curl_multi_perform");
running = 0;
status = curl_multi_perform(mMultiHandles[policy_class], &running);
}
@ -176,31 +179,34 @@ HttpService::ELoopSpeed HttpLibcurl::processTransport()
// Run completion on anything done
CURLMsg * msg(NULL);
int msgs_in_queue(0);
while ((msg = curl_multi_info_read(mMultiHandles[policy_class], &msgs_in_queue)))
{
if (CURLMSG_DONE == msg->msg)
{
CURL * handle(msg->easy_handle);
CURLcode result(msg->data.result);
{
LL_PROFILE_ZONE_NAMED_CATEGORY_NETWORK("httppt - curl_multi_info_read");
while ((msg = curl_multi_info_read(mMultiHandles[policy_class], &msgs_in_queue)))
{
if (CURLMSG_DONE == msg->msg)
{
CURL* handle(msg->easy_handle);
CURLcode result(msg->data.result);
completeRequest(mMultiHandles[policy_class], handle, result);
handle = NULL; // No longer valid on return
ret = HttpService::NORMAL; // If anything completes, we may have a free slot.
// Turning around quickly reduces connection gap by 7-10mS.
}
else if (CURLMSG_NONE == msg->msg)
{
// Ignore this... it shouldn't mean anything.
;
}
else
{
LL_WARNS_ONCE(LOG_CORE) << "Unexpected message from libcurl. Msg code: "
<< msg->msg
<< LL_ENDL;
}
msgs_in_queue = 0;
}
completeRequest(mMultiHandles[policy_class], handle, result);
handle = NULL; // No longer valid on return
ret = HttpService::NORMAL; // If anything completes, we may have a free slot.
// Turning around quickly reduces connection gap by 7-10mS.
}
else if (CURLMSG_NONE == msg->msg)
{
// Ignore this... it shouldn't mean anything.
;
}
else
{
LL_WARNS_ONCE(LOG_CORE) << "Unexpected message from libcurl. Msg code: "
<< msg->msg
<< LL_ENDL;
}
msgs_in_queue = 0;
}
}
}
if (! mActiveOps.empty())
@ -214,6 +220,7 @@ HttpService::ELoopSpeed HttpLibcurl::processTransport()
// Caller has provided us with a ref count on op.
void HttpLibcurl::addOp(const HttpOpRequest::ptr_t &op)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
llassert_always(op->mReqPolicy < mPolicyCount);
llassert_always(mMultiHandles[op->mReqPolicy] != NULL);
@ -257,6 +264,7 @@ void HttpLibcurl::addOp(const HttpOpRequest::ptr_t &op)
// method to kill the request.
bool HttpLibcurl::cancel(HttpHandle handle)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpOpRequest::ptr_t op = HttpOpRequest::fromHandle<HttpOpRequest>(handle);
active_set_t::iterator it(mActiveOps.find(op));
if (mActiveOps.end() == it)
@ -282,6 +290,7 @@ bool HttpLibcurl::cancel(HttpHandle handle)
// op to the reply queue with refcount intact.
void HttpLibcurl::cancelRequest(const HttpOpRequest::ptr_t &op)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
// Deactivate request
op->mCurlActive = false;
@ -308,6 +317,7 @@ void HttpLibcurl::cancelRequest(const HttpOpRequest::ptr_t &op)
// Keep them synchronized as necessary.
bool HttpLibcurl::completeRequest(CURLM * multi_handle, CURL * handle, CURLcode status)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpHandle ophandle(NULL);
CURLcode ccode(CURLE_OK);
@ -445,6 +455,7 @@ int HttpLibcurl::getActiveCountInClass(int policy_class) const
void HttpLibcurl::policyUpdated(int policy_class)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
if (policy_class < 0 || policy_class >= mPolicyCount || ! mMultiHandles)
{
return;

View File

@ -62,7 +62,6 @@ HttpOperation::HttpOperation():
mReplyQueue(),
mUserHandler(),
mReqPolicy(HttpRequest::DEFAULT_POLICY_ID),
mReqPriority(0U),
mTracing(HTTP_TRACE_OFF),
mMyHandle(LLCORE_HTTP_HANDLE_INVALID)
{

View File

@ -181,7 +181,6 @@ protected:
public:
// Request Data
HttpRequest::policy_t mReqPolicy;
HttpRequest::priority_t mReqPriority;
// Reply Data
HttpStatus mStatus;

View File

@ -200,6 +200,7 @@ HttpOpRequest::~HttpOpRequest()
void HttpOpRequest::stageFromRequest(HttpService * service)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpOpRequest::ptr_t self(boost::dynamic_pointer_cast<HttpOpRequest>(shared_from_this()));
service->getPolicy().addOp(self); // transfers refcount
}
@ -207,6 +208,7 @@ void HttpOpRequest::stageFromRequest(HttpService * service)
void HttpOpRequest::stageFromReady(HttpService * service)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpOpRequest::ptr_t self(boost::dynamic_pointer_cast<HttpOpRequest>(shared_from_this()));
service->getTransport().addOp(self); // transfers refcount
}
@ -214,6 +216,7 @@ void HttpOpRequest::stageFromReady(HttpService * service)
void HttpOpRequest::stageFromActive(HttpService * service)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
if (mReplyLength)
{
// If non-zero, we received and processed a Content-Range
@ -250,6 +253,7 @@ void HttpOpRequest::stageFromActive(HttpService * service)
void HttpOpRequest::visitNotifier(HttpRequest * request)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
if (mUserHandler)
{
HttpResponse * response = new HttpResponse();
@ -292,6 +296,7 @@ void HttpOpRequest::visitNotifier(HttpRequest * request)
HttpStatus HttpOpRequest::cancel()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
mStatus = HttpStatus(HttpStatus::LLCORE, HE_OP_CANCELED);
addAsReply();
@ -301,12 +306,12 @@ HttpStatus HttpOpRequest::cancel()
HttpStatus HttpOpRequest::setupGet(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers)
{
setupCommon(policy_id, priority, url, NULL, options, headers);
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
setupCommon(policy_id, url, NULL, options, headers);
mReqMethod = HOR_GET;
return HttpStatus();
@ -314,14 +319,14 @@ HttpStatus HttpOpRequest::setupGet(HttpRequest::policy_t policy_id,
HttpStatus HttpOpRequest::setupGetByteRange(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
size_t offset,
size_t len,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers)
{
setupCommon(policy_id, priority, url, NULL, options, headers);
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
setupCommon(policy_id, url, NULL, options, headers);
mReqMethod = HOR_GET;
mReqOffset = offset;
mReqLength = len;
@ -335,13 +340,13 @@ HttpStatus HttpOpRequest::setupGetByteRange(HttpRequest::policy_t policy_id,
HttpStatus HttpOpRequest::setupPost(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
BufferArray * body,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers)
{
setupCommon(policy_id, priority, url, body, options, headers);
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
setupCommon(policy_id, url, body, options, headers);
mReqMethod = HOR_POST;
return HttpStatus();
@ -349,13 +354,13 @@ HttpStatus HttpOpRequest::setupPost(HttpRequest::policy_t policy_id,
HttpStatus HttpOpRequest::setupPut(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
BufferArray * body,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers)
{
setupCommon(policy_id, priority, url, body, options, headers);
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
setupCommon(policy_id, url, body, options, headers);
mReqMethod = HOR_PUT;
return HttpStatus();
@ -363,12 +368,12 @@ HttpStatus HttpOpRequest::setupPut(HttpRequest::policy_t policy_id,
HttpStatus HttpOpRequest::setupDelete(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers)
{
setupCommon(policy_id, priority, url, NULL, options, headers);
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
setupCommon(policy_id, url, NULL, options, headers);
mReqMethod = HOR_DELETE;
return HttpStatus();
@ -376,13 +381,13 @@ HttpStatus HttpOpRequest::setupDelete(HttpRequest::policy_t policy_id,
HttpStatus HttpOpRequest::setupPatch(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
BufferArray * body,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers)
{
setupCommon(policy_id, priority, url, body, options, headers);
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
setupCommon(policy_id, url, body, options, headers);
mReqMethod = HOR_PATCH;
return HttpStatus();
@ -390,12 +395,12 @@ HttpStatus HttpOpRequest::setupPatch(HttpRequest::policy_t policy_id,
HttpStatus HttpOpRequest::setupCopy(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t &headers)
{
setupCommon(policy_id, priority, url, NULL, options, headers);
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
setupCommon(policy_id, url, NULL, options, headers);
mReqMethod = HOR_COPY;
return HttpStatus();
@ -403,12 +408,12 @@ HttpStatus HttpOpRequest::setupCopy(HttpRequest::policy_t policy_id,
HttpStatus HttpOpRequest::setupMove(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t &headers)
{
setupCommon(policy_id, priority, url, NULL, options, headers);
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
setupCommon(policy_id, url, NULL, options, headers);
mReqMethod = HOR_MOVE;
return HttpStatus();
@ -416,15 +421,14 @@ HttpStatus HttpOpRequest::setupMove(HttpRequest::policy_t policy_id,
void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
BufferArray * body,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
mProcFlags = 0U;
mReqPolicy = policy_id;
mReqPriority = priority;
mReqURL = url;
if (body)
{
@ -465,6 +469,7 @@ void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id,
// *TODO: Move this to _httplibcurl where it belongs.
HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
// Scrub transport and result data for retried op case
mCurlActive = false;
mCurlHandle = NULL;
@ -773,6 +778,7 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
size_t HttpOpRequest::writeCallback(void * data, size_t size, size_t nmemb, void * userdata)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpOpRequest::ptr_t op(HttpOpRequest::fromHandle<HttpOpRequest>(userdata));
if (! op->mReplyBody)
@ -788,6 +794,7 @@ size_t HttpOpRequest::writeCallback(void * data, size_t size, size_t nmemb, void
size_t HttpOpRequest::readCallback(void * data, size_t size, size_t nmemb, void * userdata)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpOpRequest::ptr_t op(HttpOpRequest::fromHandle<HttpOpRequest>(userdata));
if (! op->mReqBody)
@ -819,6 +826,7 @@ size_t HttpOpRequest::readCallback(void * data, size_t size, size_t nmemb, void
int HttpOpRequest::seekCallback(void *userdata, curl_off_t offset, int origin)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpOpRequest::ptr_t op(HttpOpRequest::fromHandle<HttpOpRequest>(userdata));
if (!op->mReqBody)
@ -850,6 +858,7 @@ int HttpOpRequest::seekCallback(void *userdata, curl_off_t offset, int origin)
size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, void * userdata)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
static const char status_line[] = "HTTP/";
static const size_t status_line_len = sizeof(status_line) - 1;
static const char con_ran_line[] = "content-range";
@ -999,6 +1008,7 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi
CURLcode HttpOpRequest::curlSslCtxCallback(CURL *curl, void *sslctx, void *userdata)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpOpRequest::ptr_t op(HttpOpRequest::fromHandle<HttpOpRequest>(userdata));
if (op->mCallbackSSLVerify)
@ -1025,6 +1035,7 @@ CURLcode HttpOpRequest::curlSslCtxCallback(CURL *curl, void *sslctx, void *userd
int HttpOpRequest::sslCertVerifyCallback(X509_STORE_CTX *ctx, void *param)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpOpRequest::ptr_t op(HttpOpRequest::fromHandle<HttpOpRequest>(param));
if (op->mCallbackSSLVerify)
@ -1037,6 +1048,7 @@ int HttpOpRequest::sslCertVerifyCallback(X509_STORE_CTX *ctx, void *param)
int HttpOpRequest::debugCallback(CURL * handle, curl_infotype info, char * buffer, size_t len, void * userdata)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpOpRequest::ptr_t op(HttpOpRequest::fromHandle<HttpOpRequest>(userdata));
std::string safe_line;

View File

@ -105,13 +105,11 @@ public:
/// Threading: called by application thread
///
HttpStatus setupGet(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers);
HttpStatus setupGetByteRange(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
size_t offset,
size_t len,
@ -119,40 +117,34 @@ public:
const HttpHeaders::ptr_t & headers);
HttpStatus setupPost(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
BufferArray * body,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers);
HttpStatus setupPut(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
BufferArray * body,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers);
HttpStatus setupDelete(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers);
HttpStatus setupPatch(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
BufferArray * body,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers);
HttpStatus setupCopy(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers);
HttpStatus setupMove(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers);
@ -172,7 +164,6 @@ protected:
// Threading: called by application thread
//
void setupCommon(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
BufferArray * body,
const HttpOptions::ptr_t & options,
@ -239,19 +230,6 @@ public:
/// HttpOpRequestCompare isn't an operation but a uniform comparison
/// functor for STL containers that order by priority. Mainly
/// used for the ready queue container but defined here.
class HttpOpRequestCompare
{
public:
bool operator()(const HttpOpRequest * lhs, const HttpOpRequest * rhs)
{
return lhs->mReqPriority > rhs->mReqPriority;
}
}; // end class HttpOpRequestCompare
// ---------------------------------------
// Free functions
// ---------------------------------------

View File

@ -24,6 +24,7 @@
* $/LicenseInfo$
*/
#if 0 // DEPRECATED
#include "_httpopsetpriority.h"
#include "httpresponse.h"
@ -61,3 +62,5 @@ void HttpOpSetPriority::stageFromRequest(HttpService * service)
} // end namespace LLCore
#endif

View File

@ -27,7 +27,7 @@
#ifndef _LLCORE_HTTP_SETPRIORITY_H_
#define _LLCORE_HTTP_SETPRIORITY_H_
#if 0 // DEPRECATED
#include "httpcommon.h"
#include "httprequest.h"
#include "_httpoperation.h"
@ -49,7 +49,7 @@ namespace LLCore
class HttpOpSetPriority : public HttpOperation
{
public:
HttpOpSetPriority(HttpHandle handle, HttpRequest::priority_t priority);
HttpOpSetPriority(HttpHandle handle);
virtual ~HttpOpSetPriority();
@ -63,10 +63,10 @@ public:
protected:
// Request Data
HttpHandle mHandle;
HttpRequest::priority_t mPriority;
}; // end class HttpOpSetPriority
} // end namespace LLCore
#endif
#endif // _LLCORE_HTTP_SETPRIORITY_H_

View File

@ -330,37 +330,6 @@ HttpService::ELoopSpeed HttpPolicy::processReadyQueue()
return result;
}
bool HttpPolicy::changePriority(HttpHandle handle, HttpRequest::priority_t priority)
{
for (int policy_class(0); policy_class < mClasses.size(); ++policy_class)
{
ClassState & state(*mClasses[policy_class]);
// We don't scan retry queue because a priority change there
// is meaningless. The request will be issued based on retry
// intervals not priority value, which is now moot.
// Scan ready queue for requests that match policy
HttpReadyQueue::container_type & c(state.mReadyQueue.get_container());
for (HttpReadyQueue::container_type::iterator iter(c.begin()); c.end() != iter;)
{
HttpReadyQueue::container_type::iterator cur(iter++);
if ((*cur)->getHandle() == handle)
{
HttpOpRequest::ptr_t op(*cur);
c.erase(cur); // All iterators are now invalidated
op->mReqPriority = priority;
state.mReadyQueue.push(op); // Re-insert using adapter class
return true;
}
}
}
return false;
}
bool HttpPolicy::cancel(HttpHandle handle)
{
for (int policy_class(0); policy_class < mClasses.size(); ++policy_class)

View File

@ -110,12 +110,6 @@ public:
/// Threading: called by worker thread
void retryOp(const opReqPtr_t &);
/// Attempt to change the priority of an earlier request.
/// Request that Shadows HttpService's method
///
/// Threading: called by worker thread
bool changePriority(HttpHandle handle, HttpRequest::priority_t priority);
/// Attempt to cancel a previous request.
/// Shadows HttpService's method as well
///

View File

@ -80,6 +80,7 @@ HttpService::HttpService()
HttpService::~HttpService()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
mExitRequested = 1U;
if (RUNNING == sState)
{
@ -131,6 +132,7 @@ HttpService::~HttpService()
void HttpService::init(HttpRequestQueue * queue)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
llassert_always(! sInstance);
llassert_always(NOT_INITIALIZED == sState);
sInstance = new HttpService();
@ -145,6 +147,7 @@ void HttpService::init(HttpRequestQueue * queue)
void HttpService::term()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
if (sInstance)
{
if (RUNNING == sState && sInstance->mThread)
@ -196,6 +199,7 @@ bool HttpService::isStopped()
/// Threading: callable by consumer thread *once*.
void HttpService::startThread()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
llassert_always(! mThread || STOPPED == sState);
llassert_always(INITIALIZED == sState || STOPPED == sState);
@ -220,22 +224,6 @@ void HttpService::stopRequested()
}
/// Threading: callable by worker thread.
bool HttpService::changePriority(HttpHandle handle, HttpRequest::priority_t priority)
{
bool found(false);
// Skip the request queue as we currently don't leave earlier
// requests sitting there. Start with the ready queue...
found = mPolicy->changePriority(handle, priority);
// If not there, we could try the transport/active queue but priority
// doesn't really have much effect there so we don't waste cycles.
return found;
}
/// Try to find the given request handle on any of the request
/// queues and cancel the operation.
///
@ -244,6 +232,7 @@ bool HttpService::changePriority(HttpHandle handle, HttpRequest::priority_t prio
/// Threading: callable by worker thread.
bool HttpService::cancel(HttpHandle handle)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
bool canceled(false);
// Request can't be on request queue so skip that.
@ -264,6 +253,7 @@ bool HttpService::cancel(HttpHandle handle)
/// Threading: callable by worker thread.
void HttpService::shutdown()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
// Disallow future enqueue of requests
mRequestQueue->stopQueue();
@ -293,6 +283,8 @@ void HttpService::shutdown()
// requested to stop.
void HttpService::threadRun(LLCoreInt::HttpThread * thread)
{
LL_PROFILER_SET_THREAD_NAME("HttpService");
boost::this_thread::disable_interruption di;
LLThread::registerThreadID();
@ -300,6 +292,7 @@ void HttpService::threadRun(LLCoreInt::HttpThread * thread)
ELoopSpeed loop(REQUEST_SLEEP);
while (! mExitRequested)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
try
{
loop = processRequestQueue(loop);
@ -344,6 +337,7 @@ void HttpService::threadRun(LLCoreInt::HttpThread * thread)
HttpService::ELoopSpeed HttpService::processRequestQueue(ELoopSpeed loop)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpRequestQueue::OpContainer ops;
const bool wait_for_req(REQUEST_SLEEP == loop);
@ -384,6 +378,7 @@ HttpService::ELoopSpeed HttpService::processRequestQueue(ELoopSpeed loop)
HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
long * ret_value)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range
|| opt >= HttpRequest::PO_LAST // ditto
|| (! sOptionDesc[opt].mIsLong) // datatype is long
@ -416,6 +411,7 @@ HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequ
HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
std::string * ret_value)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range
@ -443,6 +439,7 @@ HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequ
HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
HttpRequest::policyCallback_t * ret_value)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range
@ -472,6 +469,7 @@ HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequ
HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
long value, long * ret_value)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range
@ -517,6 +515,7 @@ HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequ
HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
const std::string & value, std::string * ret_value)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range
@ -548,6 +547,7 @@ HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequ
HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
HttpRequest::policyCallback_t value, HttpRequest::policyCallback_t * ret_value)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range

View File

@ -146,15 +146,6 @@ public:
/// Threading: callable by worker thread.
void shutdown();
/// Try to find the given request handle on any of the request
/// queues and reset the priority (and queue position) of the
/// request if found.
///
/// @return True if the request was found somewhere.
///
/// Threading: callable by worker thread.
bool changePriority(HttpHandle handle, HttpRequest::priority_t priority);
/// Try to find the given request handle on any of the request
/// queues and cancel the operation.
///

View File

@ -469,11 +469,11 @@ bool WorkingSet::reload(LLCore::HttpRequest * hr, LLCore::HttpOptions::ptr_t & o
LLCore::HttpHandle handle;
if (offset || length)
{
handle = hr->requestGetByteRange(0, 0, buffer, offset, length, opt, mHeaders, LLCore::HttpHandler::ptr_t(this, NoOpDeletor));
handle = hr->requestGetByteRange(0, buffer, offset, length, opt, mHeaders, LLCore::HttpHandler::ptr_t(this, NoOpDeletor));
}
else
{
handle = hr->requestGet(0, 0, buffer, opt, mHeaders, LLCore::HttpHandler::ptr_t(this, NoOpDeletor));
handle = hr->requestGet(0, buffer, opt, mHeaders, LLCore::HttpHandler::ptr_t(this, NoOpDeletor));
}
if (! handle)
{

View File

@ -32,7 +32,6 @@
#include "_httppolicy.h"
#include "_httpoperation.h"
#include "_httpoprequest.h"
#include "_httpopsetpriority.h"
#include "_httpopcancel.h"
#include "_httpopsetget.h"
@ -183,16 +182,16 @@ HttpStatus HttpRequest::getStatus() const
HttpHandle HttpRequest::requestGet(policy_t policy_id,
priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers,
HttpHandler::ptr_t user_handler)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpStatus status;
HttpOpRequest::ptr_t op(new HttpOpRequest());
if (! (status = op->setupGet(policy_id, priority, url, options, headers)))
if (! (status = op->setupGet(policy_id, url, options, headers)))
{
mLastReqStatus = status;
return LLCORE_HTTP_HANDLE_INVALID;
@ -210,7 +209,6 @@ HttpHandle HttpRequest::requestGet(policy_t policy_id,
HttpHandle HttpRequest::requestGetByteRange(policy_t policy_id,
priority_t priority,
const std::string & url,
size_t offset,
size_t len,
@ -218,10 +216,11 @@ HttpHandle HttpRequest::requestGetByteRange(policy_t policy_id,
const HttpHeaders::ptr_t & headers,
HttpHandler::ptr_t user_handler)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
HttpStatus status;
HttpOpRequest::ptr_t op(new HttpOpRequest());
if (! (status = op->setupGetByteRange(policy_id, priority, url, offset, len, options, headers)))
if (! (status = op->setupGetByteRange(policy_id, url, offset, len, options, headers)))
{
mLastReqStatus = status;
return LLCORE_HTTP_HANDLE_INVALID;
@ -239,7 +238,6 @@ HttpHandle HttpRequest::requestGetByteRange(policy_t policy_id,
HttpHandle HttpRequest::requestPost(policy_t policy_id,
priority_t priority,
const std::string & url,
BufferArray * body,
const HttpOptions::ptr_t & options,
@ -249,7 +247,7 @@ HttpHandle HttpRequest::requestPost(policy_t policy_id,
HttpStatus status;
HttpOpRequest::ptr_t op(new HttpOpRequest());
if (! (status = op->setupPost(policy_id, priority, url, body, options, headers)))
if (! (status = op->setupPost(policy_id, url, body, options, headers)))
{
mLastReqStatus = status;
return LLCORE_HTTP_HANDLE_INVALID;
@ -267,7 +265,6 @@ HttpHandle HttpRequest::requestPost(policy_t policy_id,
HttpHandle HttpRequest::requestPut(policy_t policy_id,
priority_t priority,
const std::string & url,
BufferArray * body,
const HttpOptions::ptr_t & options,
@ -277,7 +274,7 @@ HttpHandle HttpRequest::requestPut(policy_t policy_id,
HttpStatus status;
HttpOpRequest::ptr_t op (new HttpOpRequest());
if (! (status = op->setupPut(policy_id, priority, url, body, options, headers)))
if (! (status = op->setupPut(policy_id, url, body, options, headers)))
{
mLastReqStatus = status;
return LLCORE_HTTP_HANDLE_INVALID;
@ -294,7 +291,6 @@ HttpHandle HttpRequest::requestPut(policy_t policy_id,
}
HttpHandle HttpRequest::requestDelete(policy_t policy_id,
priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers,
@ -303,7 +299,7 @@ HttpHandle HttpRequest::requestDelete(policy_t policy_id,
HttpStatus status;
HttpOpRequest::ptr_t op(new HttpOpRequest());
if (!(status = op->setupDelete(policy_id, priority, url, options, headers)))
if (!(status = op->setupDelete(policy_id, url, options, headers)))
{
mLastReqStatus = status;
return LLCORE_HTTP_HANDLE_INVALID;
@ -320,7 +316,6 @@ HttpHandle HttpRequest::requestDelete(policy_t policy_id,
}
HttpHandle HttpRequest::requestPatch(policy_t policy_id,
priority_t priority,
const std::string & url,
BufferArray * body,
const HttpOptions::ptr_t & options,
@ -330,7 +325,7 @@ HttpHandle HttpRequest::requestPatch(policy_t policy_id,
HttpStatus status;
HttpOpRequest::ptr_t op (new HttpOpRequest());
if (!(status = op->setupPatch(policy_id, priority, url, body, options, headers)))
if (!(status = op->setupPatch(policy_id, url, body, options, headers)))
{
mLastReqStatus = status;
return LLCORE_HTTP_HANDLE_INVALID;
@ -347,7 +342,6 @@ HttpHandle HttpRequest::requestPatch(policy_t policy_id,
}
HttpHandle HttpRequest::requestCopy(policy_t policy_id,
priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers,
@ -356,7 +350,7 @@ HttpHandle HttpRequest::requestCopy(policy_t policy_id,
HttpStatus status;
HttpOpRequest::ptr_t op(new HttpOpRequest());
if (!(status = op->setupCopy(policy_id, priority, url, options, headers)))
if (!(status = op->setupCopy(policy_id, url, options, headers)))
{
mLastReqStatus = status;
return LLCORE_HTTP_HANDLE_INVALID;
@ -374,7 +368,6 @@ HttpHandle HttpRequest::requestCopy(policy_t policy_id,
}
HttpHandle HttpRequest::requestMove(policy_t policy_id,
priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers,
@ -383,7 +376,7 @@ HttpHandle HttpRequest::requestMove(policy_t policy_id,
HttpStatus status;
HttpOpRequest::ptr_t op (new HttpOpRequest());
if (!(status = op->setupMove(policy_id, priority, url, options, headers)))
if (!(status = op->setupMove(policy_id, url, options, headers)))
{
mLastReqStatus = status;
return LLCORE_HTTP_HANDLE_INVALID;
@ -483,24 +476,6 @@ HttpHandle HttpRequest::requestCancel(HttpHandle request, HttpHandler::ptr_t use
}
HttpHandle HttpRequest::requestSetPriority(HttpHandle request, priority_t priority,
HttpHandler::ptr_t handler)
{
HttpStatus status;
HttpOperation::ptr_t op (new HttpOpSetPriority(request, priority));
op->setReplyPath(mReplyQueue, handler);
if (! (status = mRequestQueue->addOp(op))) // transfers refcount
{
mLastReqStatus = status;
return LLCORE_HTTP_HANDLE_INVALID;
}
mLastReqStatus = status;
return op->getHandle();
}
// ====================================
// Utility Methods
// ====================================

View File

@ -95,7 +95,6 @@ private:
public:
typedef unsigned int policy_t;
typedef unsigned int priority_t;
typedef boost::shared_ptr<HttpRequest> ptr_t;
typedef boost::weak_ptr<HttpRequest> wptr_t;
@ -316,8 +315,6 @@ public:
///
/// @param policy_id Default or user-defined policy class under
/// which this request is to be serviced.
/// @param priority Standard priority scheme inherited from
/// Indra code base (U32-type scheme).
/// @param url URL with any encoded query parameters to
/// be accessed.
/// @param options Optional instance of an HttpOptions object
@ -346,7 +343,6 @@ public:
/// case, @see getStatus() will return more info.
///
HttpHandle requestGet(policy_t policy_id,
priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers,
@ -377,7 +373,6 @@ public:
/// - Referer:
///
/// @param policy_id @see requestGet()
/// @param priority "
/// @param url "
/// @param offset Offset of first byte into resource to be returned.
/// @param len Count of bytes to be returned
@ -387,7 +382,6 @@ public:
/// @return "
///
HttpHandle requestGetByteRange(policy_t policy_id,
priority_t priority,
const std::string & url,
size_t offset,
size_t len,
@ -418,7 +412,6 @@ public:
/// - Expect:
///
/// @param policy_id @see requestGet()
/// @param priority "
/// @param url "
/// @param body Byte stream to be sent as the body. No
/// further encoding or escaping will be done
@ -429,7 +422,6 @@ public:
/// @return "
///
HttpHandle requestPost(policy_t policy_id,
priority_t priority,
const std::string & url,
BufferArray * body,
const HttpOptions::ptr_t & options,
@ -459,7 +451,6 @@ public:
/// - Content-Type:
///
/// @param policy_id @see requestGet()
/// @param priority "
/// @param url "
/// @param body Byte stream to be sent as the body. No
/// further encoding or escaping will be done
@ -470,7 +461,6 @@ public:
/// @return "
///
HttpHandle requestPut(policy_t policy_id,
priority_t priority,
const std::string & url,
BufferArray * body,
const HttpOptions::ptr_t & options,
@ -483,7 +473,6 @@ public:
/// encoding and communicating the content types.
///
/// @param policy_id @see requestGet()
/// @param priority "
/// @param url "
/// @param options @see requestGet()K(optional)
/// @param headers "
@ -491,7 +480,6 @@ public:
/// @return "
///
HttpHandle requestDelete(policy_t policy_id,
priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers,
@ -502,7 +490,6 @@ public:
/// encoding and communicating the content types.
///
/// @param policy_id @see requestGet()
/// @param priority "
/// @param url "
/// @param body Byte stream to be sent as the body. No
/// further encoding or escaping will be done
@ -513,7 +500,6 @@ public:
/// @return "
///
HttpHandle requestPatch(policy_t policy_id,
priority_t priority,
const std::string & url,
BufferArray * body,
const HttpOptions::ptr_t & options,
@ -525,7 +511,6 @@ public:
/// encoding and communicating the content types.
///
/// @param policy_id @see requestGet()
/// @param priority "
/// @param url "
/// @param options @see requestGet()K(optional)
/// @param headers "
@ -533,7 +518,6 @@ public:
/// @return "
///
HttpHandle requestCopy(policy_t policy_id,
priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers,
@ -544,7 +528,6 @@ public:
/// encoding and communicating the content types.
///
/// @param policy_id @see requestGet()
/// @param priority "
/// @param url "
/// @param options @see requestGet()K(optional)
/// @param headers "
@ -552,7 +535,6 @@ public:
/// @return "
///
HttpHandle requestMove(policy_t policy_id,
priority_t priority,
const std::string & url,
const HttpOptions::ptr_t & options,
const HttpHeaders::ptr_t & headers,
@ -593,18 +575,6 @@ public:
HttpHandle requestCancel(HttpHandle request, HttpHandler::ptr_t);
/// Request that a previously-issued request be reprioritized.
/// The status of whether the change itself succeeded arrives
/// via notification.
///
/// @param request Handle of previously-issued request to
/// be changed.
/// @param priority New priority value.
/// @param handler @see requestGet()
/// @return "
///
HttpHandle requestSetPriority(HttpHandle request, priority_t priority, HttpHandler::ptr_t handler);
/// @}
/// @name UtilityMethods

View File

@ -614,7 +614,6 @@ void HttpRequestTestObjectType::test<7>()
// Issue a GET that can't connect
mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT);
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
0U,
"http://127.0.0.1:2/nothing/here",
0,
0,
@ -716,7 +715,6 @@ void HttpRequestTestObjectType::test<8>()
// Issue a GET that *can* connect
mStatus = HttpStatus(200);
HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base,
HttpOptions::ptr_t(),
HttpHeaders::ptr_t(),
@ -812,7 +810,6 @@ void HttpRequestTestObjectType::test<9>()
// Issue a GET that *can* connect
mStatus = HttpStatus(200);
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base,
0,
0,
@ -913,7 +910,6 @@ void HttpRequestTestObjectType::test<10>()
body->append(body_text, strlen(body_text));
mStatus = HttpStatus(200);
HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base,
body,
HttpOptions::ptr_t(),
@ -1020,7 +1016,6 @@ void HttpRequestTestObjectType::test<11>()
body->append(body_text, strlen(body_text));
mStatus = HttpStatus(200);
HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base,
body,
HttpOptions::ptr_t(),
@ -1127,7 +1122,6 @@ void HttpRequestTestObjectType::test<12>()
// Issue a GET that *can* connect
mStatus = HttpStatus(200);
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base,
0,
0,
@ -1240,7 +1234,6 @@ void HttpRequestTestObjectType::test<13>()
regex_container_t::value_type(boost::regex("X-LL-Special", boost::regex::icase),
boost::regex(".*", boost::regex::icase)));
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base,
0,
0,
@ -1346,7 +1339,6 @@ void HttpRequestTestObjectType::test<14>()
// Issue a GET that sleeps
mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_OPERATION_TIMEDOUT);
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base,
0,
0,
@ -1454,7 +1446,6 @@ void HttpRequestTestObjectType::test<15>()
mStatus = HttpStatus(200);
handler.mCheckContentType = "application/llsd+xml";
HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base,
HttpOptions::ptr_t(),
HttpHeaders::ptr_t(),
@ -1609,7 +1600,6 @@ void HttpRequestTestObjectType::test<16>()
boost::regex("X-Reflect-content-encoding", boost::regex::icase),
boost::regex(".*", boost::regex::icase)));
HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + "reflect/",
options,
HttpHeaders::ptr_t(),
@ -1684,7 +1674,6 @@ void HttpRequestTestObjectType::test<16>()
boost::regex("X-Reflect-content-encoding", boost::regex::icase),
boost::regex(".*", boost::regex::icase)));
handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + "reflect/",
0,
47,
@ -1863,7 +1852,6 @@ void HttpRequestTestObjectType::test<17>()
boost::regex("X-Reflect-transfer_encoding", boost::regex::icase),
boost::regex(".*chunked.*", boost::regex::icase)));
HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + "reflect/",
ba,
options,
@ -2049,7 +2037,6 @@ void HttpRequestTestObjectType::test<18>()
boost::regex(".*", boost::regex::icase)));
HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + "reflect/",
ba,
options,
@ -2249,7 +2236,6 @@ void HttpRequestTestObjectType::test<19>()
boost::regex("X-Reflect-content-encoding", boost::regex::icase),
boost::regex(".*", boost::regex::icase)));
HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + "reflect/",
options,
headers,
@ -2457,7 +2443,6 @@ void HttpRequestTestObjectType::test<20>()
boost::regex(".*", boost::regex::icase)));
HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + "reflect/",
ba,
options,
@ -2666,7 +2651,6 @@ void HttpRequestTestObjectType::test<21>()
boost::regex("X-Reflect-content-type", boost::regex::icase),
boost::regex("text/html", boost::regex::icase)));
HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + "reflect/",
ba,
options,
@ -2797,7 +2781,6 @@ void HttpRequestTestObjectType::test<22>()
char buffer[128];
sprintf(buffer, "/bug2295/%d/", i);
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + buffer,
0,
25,
@ -2829,7 +2812,6 @@ void HttpRequestTestObjectType::test<22>()
char buffer[128];
sprintf(buffer, "/bug2295/00000012/%d/", i);
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + buffer,
0,
25,
@ -2861,7 +2843,6 @@ void HttpRequestTestObjectType::test<22>()
char buffer[128];
sprintf(buffer, "/bug2295/inv_cont_range/%d/", i);
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base + buffer,
0,
25,
@ -2984,7 +2965,6 @@ void HttpRequestTestObjectType::test<23>()
std::ostringstream url;
url << url_base << i << "/";
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
0U,
url.str(),
0,
0,

View File

@ -418,7 +418,7 @@ bool LLCrashLogger::runCrashLogPost(std::string host, LLSD data, std::string msg
updateApplication(llformat("%s, try %d...", msg.c_str(), i+1));
LL_INFOS("CRASHREPORT") << "POST crash data to " << host << LL_ENDL;
LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(httpRequest.get(), LLCore::HttpRequest::DEFAULT_POLICY_ID, 0,
LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(httpRequest.get(), LLCore::HttpRequest::DEFAULT_POLICY_ID,
host, data, httpOpts, LLCore::HttpHeaders::ptr_t(), LLCore::HttpHandler::ptr_t(new LLCrashLoggerHandler));
if (handle == LLCORE_HTTP_HANDLE_INVALID)

View File

@ -221,6 +221,7 @@ const std::string LLDiskCache::assetTypeToString(LLAssetType::EType at)
{ LLAssetType::AT_PERSON, "PERSON" },
{ LLAssetType::AT_MESH, "MESH" },
{ LLAssetType::AT_SETTINGS, "SETTINGS" },
{ LLAssetType::AT_MATERIAL, "MATERIAL" },
{ LLAssetType::AT_UNKNOWN, "UNKNOWN" }
};

View File

@ -45,8 +45,7 @@ void LLLFSThread::initClass(bool local_is_threaded)
//static
S32 LLLFSThread::updateClass(U32 ms_elapsed)
{
sLocal->update((F32)ms_elapsed);
return sLocal->getPending();
return sLocal->update((F32)ms_elapsed);
}
//static
@ -58,6 +57,7 @@ void LLLFSThread::cleanupClass()
{
sLocal->update(0);
}
sLocal->shutdown();
delete sLocal;
sLocal = NULL;
}
@ -65,8 +65,7 @@ void LLLFSThread::cleanupClass()
//----------------------------------------------------------------------------
LLLFSThread::LLLFSThread(bool threaded) :
LLQueuedThread("LFS", threaded),
mPriorityCounter(PRIORITY_LOWBITS)
LLQueuedThread("LFS", threaded)
{
if(!mLocalAPRFilePoolp)
{
@ -84,14 +83,12 @@ LLLFSThread::~LLLFSThread()
LLLFSThread::handle_t LLLFSThread::read(const std::string& filename, /* Flawfinder: ignore */
U8* buffer, S32 offset, S32 numbytes,
Responder* responder, U32 priority)
Responder* responder)
{
LL_PROFILE_ZONE_SCOPED;
handle_t handle = generateHandle();
if (priority == 0) priority = PRIORITY_NORMAL | priorityCounter();
else if (priority < PRIORITY_LOW) priority |= PRIORITY_LOW; // All reads are at least PRIORITY_LOW
Request* req = new Request(this, handle, priority,
Request* req = new Request(this, handle,
FILE_READ, filename,
buffer, offset, numbytes,
responder);
@ -107,13 +104,12 @@ LLLFSThread::handle_t LLLFSThread::read(const std::string& filename, /* Flawfind
LLLFSThread::handle_t LLLFSThread::write(const std::string& filename,
U8* buffer, S32 offset, S32 numbytes,
Responder* responder, U32 priority)
Responder* responder)
{
LL_PROFILE_ZONE_SCOPED;
handle_t handle = generateHandle();
if (priority == 0) priority = PRIORITY_LOW | priorityCounter();
Request* req = new Request(this, handle, priority,
Request* req = new Request(this, handle,
FILE_WRITE, filename,
buffer, offset, numbytes,
responder);
@ -130,11 +126,11 @@ LLLFSThread::handle_t LLLFSThread::write(const std::string& filename,
//============================================================================
LLLFSThread::Request::Request(LLLFSThread* thread,
handle_t handle, U32 priority,
handle_t handle,
operation_t op, const std::string& filename,
U8* buffer, S32 offset, S32 numbytes,
Responder* responder) :
QueuedRequest(handle, priority, FLAG_AUTO_COMPLETE),
QueuedRequest(handle, FLAG_AUTO_COMPLETE),
mThread(thread),
mOperation(op),
mFileName(filename),
@ -157,6 +153,7 @@ LLLFSThread::Request::~Request()
// virtual, called from own thread
void LLLFSThread::Request::finishRequest(bool completed)
{
LL_PROFILE_ZONE_SCOPED;
if (mResponder.notNull())
{
mResponder->completed(completed ? mBytesRead : 0);
@ -166,6 +163,7 @@ void LLLFSThread::Request::finishRequest(bool completed)
void LLLFSThread::Request::deleteRequest()
{
LL_PROFILE_ZONE_SCOPED;
if (getStatus() == STATUS_QUEUED)
{
LL_ERRS() << "Attempt to delete a queued LLLFSThread::Request!" << LL_ENDL;
@ -180,6 +178,7 @@ void LLLFSThread::Request::deleteRequest()
bool LLLFSThread::Request::processRequest()
{
LL_PROFILE_ZONE_SCOPED;
bool complete = false;
if (mOperation == FILE_READ)
{

Some files were not shown because too many files have changed in this diff Show More