diff --git a/.gitignore b/.gitignore
index a73a85e84f..4af34870cf 100755
--- a/.gitignore
+++ b/.gitignore
@@ -13,14 +13,17 @@
LICENSES
build-darwin-*
build-linux-*
-build-stamp
-build-vc120*
-build-vc150*
-configure-stamp
debian/files
debian/secondlife-appearance-utility*
debian/secondlife-viewer*
indra/.distcc
+build-vc80/
+build-vc100/
+build-vc120/
+build-vc120-32/
+build-vc120-64/
+build-vc150-32/
+build-vc150-64/
indra/CMakeFiles
indra/build-vc[0-9]*
indra/lib/mono/1.0/*.dll
diff --git a/autobuild.xml b/autobuild.xml
index ef07179e52..e6e92b93d3 100644
--- a/autobuild.xml
+++ b/autobuild.xml
@@ -76,9 +76,9 @@
archive
name
darwin64
@@ -112,9 +112,9 @@
archive
name
windows
@@ -124,16 +124,16 @@
archive
name
windows64
version
- 1.4.5.504800
+ 1.4.5.539073
boost
version
- 1.57
+ 1.72
bugsplat
@@ -244,9 +244,9 @@
archive
hash
- c3b5e8c57bd1c92bc9e0956586908b99
+ 471b0b350955152fd87518575057dfc4
url
- http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/26330/207568/bugsplat-1.0.7.520791-darwin64-520791.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/60326/566593/bugsplat-1.0.7.542667-darwin64-542667.tar.bz2
name
darwin64
@@ -256,9 +256,9 @@
archive
hash
- 766dfde65a5b42ea5691d41df79c43e0
+ 70e8bf46145c4cbae6f93e8b70ba5499
url
- http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/26332/207582/bugsplat-3.6.0.4.520791-windows-520791.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/60320/566541/bugsplat-3.6.0.4.542667-windows-542667.tar.bz2
name
windows
@@ -268,16 +268,16 @@
archive
hash
- afd01285e22f27d473fac6f88fac9a3b
+ a73696e859fad3f19f835740815a2bd3
url
- http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/26331/207576/bugsplat-3.6.0.4.520791-windows64-520791.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/60321/566542/bugsplat-3.6.0.4.542667-windows64-542667.tar.bz2
name
windows64
version
- 1.0.7.520791
+ 1.0.7.542667
colladadom
@@ -296,7 +296,7 @@
archive
hash
- 66849777a83cb69cec3c06b07da7cd3d
+ 726bc31e562752f081e95e8fcc70e405
url
http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/colladadom_3p-update-colladadom/rev/297450/arch/Darwin/installer/colladadom-2.3.297450-darwin-297450.tar.bz2
@@ -308,9 +308,9 @@
archive
hash
- fa93a9a10fa379091e3e7b85665690d9
+ 02e6a8207dcdaf243dcb6da19b8c3534
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/913/2026/colladadom-2.3.500902-darwin64-500902.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/64099/601302/colladadom-2.3.545362-darwin64-545362.tar.bz2
name
darwin64
@@ -332,9 +332,9 @@
archive
hash
- 868127582794d6fd32fa69c9be4e83e4
+ c90613240ba3e3a171d3379275ae4ee3
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/912/2031/colladadom-2.3.500902-linux64-500902.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9695/45732/colladadom-2.3.509683-linux64-509683.tar.bz2
name
linux64
@@ -344,9 +344,9 @@
archive
hash
- 5bd7875e16e7f88e21f4c44fe7c6433f
+ 8a02a10fc69c8f504dc5335644db184a
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/915/2035/colladadom-2.3.500902-windows-500902.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/64104/601313/colladadom-2.3.545362-windows-545362.tar.bz2
name
windows
@@ -356,16 +356,16 @@
archive
hash
- 8a647129a0a0a31594557785ea85f840
+ 742180324fca7ab92b6a61a36aab4f9d
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/914/2034/colladadom-2.3.500902-windows64-500902.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/64103/601314/colladadom-2.3.545362-windows64-545362.tar.bz2
name
windows64
version
- 2.3.500902
+ 2.3.545362
curl
@@ -398,9 +398,9 @@
archive
hash
- f426c56252c70fe38fcb2251f7c1d762
+ f5ae57117a6518d11f49ccfbfbe0969d
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9265/41615/curl-7.54.1.509254-darwin64-509254.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/64131/601402/curl-7.54.1.545369-darwin64-545369.tar.bz2
name
darwin64
@@ -434,11 +434,11 @@
archive
hash
- 4c7a960e1ee518acceac6a0c65495800
+ 2796ae7b09e730a55ac03f74ed669520
hash_algorithm
md5
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9268/41606/curl-7.54.1.509254-windows-509254.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/64130/601396/curl-7.54.1.545369-windows-545369.tar.bz2
name
windows
@@ -448,16 +448,16 @@
archive
hash
- 32df7cce1658ccec893fb46cd476c024
+ a8f96e5cdb8128b23d49ff4c3f2233a4
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9267/41607/curl-7.54.1.509254-windows64-509254.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/64129/601382/curl-7.54.1.545369-windows64-545369.tar.bz2
name
windows64
version
- 7.54.1.509254
+ 7.54.1.545369
db
@@ -550,16 +550,16 @@
archive
hash
- 2fa9e9e89a81ed2ed686a170681f6bbc
+ d778c6a3475bc35ee8b9615dfc38b4a9
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/571/1225/dictionaries-1.500564-common-500564.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55025/511964/dictionaries-1.538984-common-538984.tar.bz2
name
common
version
- 1.500564
+ 1.538984
dullahan
@@ -580,9 +580,9 @@
archive
hash
- 350866eec6be17ffc265904b91dcfe6b
+ e145f8ea99a21712434e0e868d1885dc
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/60900/572290/dullahan-1.7.0.202005311125_81.3.10_gb223419_chromium-81.0.4044.138-darwin64-543086.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/62333/588183/dullahan-1.7.0.202006240858_81.3.10_gb223419_chromium-81.0.4044.138-darwin64-544091.tar.bz2
name
darwin64
@@ -592,9 +592,9 @@
archive
hash
- aa4faf9ef9057362d63f8d57092506b3
+ fdbbbfc377e28cba664f2b1c54ea6086
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/60902/572301/dullahan-1.7.0.202005311828_81.3.10_gb223419_chromium-81.0.4044.138-windows-543086.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/62331/588162/dullahan-1.7.0.202006241556_81.3.10_gb223419_chromium-81.0.4044.138-windows-544091.tar.bz2
name
windows
@@ -604,16 +604,16 @@
archive
hash
- 6e29ea2ccdad80dcf1b5dc974932c1f6
+ d85a32d905b199534e8feafa34b28e39
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/60901/572302/dullahan-1.7.0.202005311828_81.3.10_gb223419_chromium-81.0.4044.138-windows64-543086.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/62332/588168/dullahan-1.7.0.202006241556_81.3.10_gb223419_chromium-81.0.4044.138-windows64-544091.tar.bz2
name
windows64
version
- 1.7.0.202005311828_81.3.10_gb223419_chromium-81.0.4044.138
+ 1.7.0.202006240858_81.3.10_gb223419_chromium-81.0.4044.138
elfio
@@ -670,9 +670,9 @@
archive
hash
- fd182ab5bed66c94899dec3035310945
+ 3656b7f7b655cb267fd94f089d2e145c
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/384/954/expat-2.1.1.500375-darwin64-500375.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54860/510198/expat-2.1.1.538990-darwin64-538990.tar.bz2
name
darwin64
@@ -706,9 +706,9 @@
archive
hash
- 09ece3f04ec0bd21dd0d401235aa20f7
+ c509f8afa1e02f4c16232cce7f6855f8
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/383/949/expat-2.1.1.500375-windows-500375.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55056/512080/expat-2.1.1.538990-windows-538990.tar.bz2
name
windows
@@ -718,16 +718,16 @@
archive
hash
- 5c82a3482799fe22b3c8fcb317f87bbb
+ aba97cfdf44c04dbfcac89c7cb472580
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/382/946/expat-2.1.1.500375-windows64-500375.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55054/512068/expat-2.1.1.538990-windows64-538990.tar.bz2
name
windows64
version
- 2.1.1.500375
+ 2.1.1.538990
fmodstudio
@@ -880,9 +880,9 @@
archive
hash
- 3f0698d53acf14b3f0a11dba889d67f3
+ 81a2e9aca3e33c4eecf0081854540b07
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/875/1919/freetype-2.4.4.500865-darwin64-500865.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/56309/526711/freetype-2.4.4.539865-darwin64-539865.tar.bz2
name
darwin64
@@ -916,9 +916,9 @@
archive
hash
- b7a8df22cfc910180c66bb1c1ed89cd4
+ 1d1c7b60f71a5152ced60bee87f5bba8
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/876/1922/freetype-2.4.4.500865-windows-500865.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/56312/526734/freetype-2.4.4.539865-windows-539865.tar.bz2
name
windows
@@ -928,16 +928,16 @@
archive
hash
- ff72a895012ed603935083496b0a7bc9
+ 53e78d4a607e959637e98a82a3cf5bea
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/877/1925/freetype-2.4.4.500865-windows64-500865.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/56310/526723/freetype-2.4.4.539865-windows64-539865.tar.bz2
name
windows64
version
- 2.4.4.500865
+ 2.4.4.539865
glext
@@ -953,6 +953,18 @@
glext
platforms
+ darwin64
+
+ archive
+
+ hash
+ 1bd3214ac23474ea4c869e386970a1be
+ url
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54835/510029/glext-68-darwin64-538965.tar.bz2
+
+ name
+ darwin64
+
linux
archive
@@ -982,9 +994,9 @@
archive
hash
- 731d4adecfcbd9f7d20c4bbd2c183962
+ 6a311615bce59b01cf73ee65012a9b38
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/p64_3p-glext/rev/314200/arch/CYGWIN/installer/glext-68-windows-314200.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54951/511711/glext-68-windows-538965.tar.bz2
name
windows
@@ -994,9 +1006,9 @@
archive
hash
- 9635e7e6fded468dfc0874a2ead54123
+ daf619dab1cf7518af6532b18800c4b0
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/p64_3p-glext/rev/314200/arch/CYGWIN/installer/glext-68-windows64-314200.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54924/511490/glext-68-windows64-538965.tar.bz2
name
windows64
@@ -1024,9 +1036,9 @@
archive
hash
- fa41756977ad8b9fd2d1465dadd4f956
+ 650e836255b6c2ecb93d3f1f7220051c
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/529/1139/glh_linear-0.0.0-common-500522.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55011/511905/glh_linear-0.0.0-common-538981.tar.bz2
name
common
@@ -1066,9 +1078,9 @@
archive
hash
- 017ef34ddf14293099a90c6eaa3615ca
+ 343913fe1434da228c2210c23d2e3a1a
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/1626/3627/glod-1.0pre3.501614-darwin64-501614.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54850/510134/glod-1.0pre3.538980-darwin64-538980.tar.bz2
name
darwin64
@@ -1104,11 +1116,11 @@
archive
hash
- 573e68f46f825a1c040daa4994ee2a61
+ e36c95b0d0fbaa3ff3392facaf5de447
hash_algorithm
md5
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/1627/3633/glod-1.0pre3.501614-windows-501614.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55008/511893/glod-1.0pre3.538980-windows-538980.tar.bz2
name
windows
@@ -1118,16 +1130,16 @@
archive
hash
- f8362e1a2f4d03d99c6231101d3d472e
+ 6302ee1903ab419e76565d9eb6acd274
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/1628/3638/glod-1.0pre3.501614-windows64-501614.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55004/511885/glod-1.0pre3.538980-windows64-538980.tar.bz2
name
windows64
version
- 1.0pre3.501614
+ 1.0pre3.538980
google_breakpad
@@ -1160,9 +1172,9 @@
archive
hash
- 2d43c6a149cd9c89ba19e884579b1e25
+ ca33f234aae399b9e704e262f7e15d35
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/1836/4096/google_breakpad-1413.501824-darwin64-501824.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/56338/526869/google_breakpad-1413.539880-darwin64-539880.tar.bz2
name
darwin64
@@ -1196,9 +1208,9 @@
archive
hash
- 6a7929c7280a5c9b528fdd334da5c2d1
+ bfee0438617f57f02f7e8515a801cb20
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/1838/4108/google_breakpad-1413.501824-windows-501824.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/56359/526982/google_breakpad-1413.539880-windows-539880.tar.bz2
name
windows
@@ -1208,16 +1220,16 @@
archive
hash
- 4fb761717f3ce6ccabdaeb009272b7ca
+ 6f983e754bb3046f065806b510b408c5
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/1837/4103/google_breakpad-1413.501824-windows64-501824.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/56358/526975/google_breakpad-1413.539880-windows64-539880.tar.bz2
name
windows64
version
- 1413.501824
+ 1413.539880
googlemock
@@ -1250,9 +1262,9 @@
archive
hash
- 1a8081953bdf1bbbc9b8a8e6e062c02d
+ f9831360ced94943ab9dfb3fbf5256d3
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/919/2048/googlemock-1.7.0.500908-darwin64-500908.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/64101/601290/googlemock-1.7.0.545363-darwin64-545363.tar.bz2
name
darwin64
@@ -1274,9 +1286,9 @@
archive
hash
- 0f606bf01f933f00edeb9bf9a2530930
+ ff459b58695c76838782847a0b792104
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/918/2056/googlemock-1.7.0.500908-linux64-500908.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9697/45717/googlemock-1.7.0.509686-linux64-509686.tar.bz2
name
linux64
@@ -1286,9 +1298,9 @@
archive
hash
- d01c9b12be6c5bb0749441495d45cba3
+ 8149e46b4f7abb3ac284415cfe1366e1
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/920/2051/googlemock-1.7.0.500908-windows-500908.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/64102/601296/googlemock-1.7.0.545363-windows-545363.tar.bz2
name
windows
@@ -1298,16 +1310,16 @@
archive
hash
- e508a2ac7900853cc551666d0cf06541
+ f3851eba809ead2810d702041569d36d
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/921/2059/googlemock-1.7.0.500908-windows64-500908.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/64100/601284/googlemock-1.7.0.545363-windows64-545363.tar.bz2
name
windows64
version
- 1.7.0.500908
+ 1.7.0.545363
gstreamer
@@ -1420,9 +1432,9 @@
archive
hash
- a0c4405c9e44d4a0135fe20ba8cfbace
+ ba229348c1d9d58519cd854ff9d8ef3d
url
- http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/4693/14627/havok_source-2012.1-2-darwin64-504680.tar.bz2
+ http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/55213/512968/havok_source-2012.1-2-darwin64-539117.tar.bz2
name
darwin64
@@ -1456,9 +1468,9 @@
archive
hash
- 035572a1929be66f6c56468e0ef7fe74
+ 4ff2af85106907acb171bb1e38a3757e
url
- http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/4695/14637/havok_source-2012.1-2-windows-504680.tar.bz2
+ http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/55214/512993/havok_source-2012.1-2-windows-539117.tar.bz2
name
windows
@@ -1468,9 +1480,9 @@
archive
hash
- d8525d2fbb9e0f7bc31427b47350e468
+ bcaf4631ea10f7d09eecb73e8f5bef6c
url
- http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/4694/14634/havok_source-2012.1-2-windows64-504680.tar.bz2
+ http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/55212/512962/havok_source-2012.1-2-windows64-539117.tar.bz2
name
windows64
@@ -1510,9 +1522,9 @@
archive
hash
- 4e7fef9c6ae9b7ccf19b7fdb96912b9c
+ 3f2e34e3a2dac8eea957cad143a71dc5
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/3152/7571/jpeglib-8c.503140-darwin64-503140.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54847/510113/jpeglib-8c.538977-darwin64-538977.tar.bz2
name
darwin64
@@ -1546,9 +1558,9 @@
archive
hash
- 00523662f6a7388377166e9415e113e9
+ c8dee00ef13af40ec68becc25830e195
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/3153/7557/jpeglib-8c.503140-windows-503140.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54992/511854/jpeglib-8c.538977-windows-538977.tar.bz2
name
windows
@@ -1558,16 +1570,16 @@
archive
hash
- 70ed49ed2317b6dba9af1f186956ac79
+ 6f40620e86f3c9b91b6b5fe3c81776fc
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/3154/7558/jpeglib-8c.503140-windows64-503140.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54991/511847/jpeglib-8c.538977-windows64-538977.tar.bz2
name
windows64
version
- 8c.503140
+ 8c.538977
jsoncpp
@@ -1600,9 +1612,9 @@
archive
hash
- 3564da2ab285a8652d2ee157d1f167e2
+ 87d32aaac4183590c96edd0b6d9bf3e4
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/1478/3283/jsoncpp-0.5.0.501464-darwin64-501464.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54846/510106/jsoncpp-0.5.0.538976-darwin64-538976.tar.bz2
name
darwin64
@@ -1636,9 +1648,9 @@
archive
hash
- ed25115f3e53e59d4d26e0953c273648
+ b73d9addab278eacc100bd312ab6ec5c
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/1476/3277/jsoncpp-0.5.0.501464-windows-501464.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54990/511840/jsoncpp-0.5.0.538976-windows-538976.tar.bz2
name
windows
@@ -1648,16 +1660,16 @@
archive
hash
- b328db840fd28532be39556d130c9439
+ 1b9ac5708cc526d2c5358ef0a427109d
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/1477/3284/jsoncpp-0.5.0.501464-windows64-501464.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54989/511833/jsoncpp-0.5.0.538976-windows64-538976.tar.bz2
name
windows64
version
- 0.5.0.501464
+ 0.5.0.538976
kdu
@@ -1690,9 +1702,9 @@
archive
hash
- d1521becaf21bf7233173722af63f57d
+ ccfd8eacd1ebe92715944094064ba2e4
url
- http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/15257/98440/kdu-7.10.4.513518-darwin64-513518.tar.bz2
+ http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/55187/512570/kdu-7.10.4.539108-darwin64-539108.tar.bz2
name
darwin64
@@ -1726,9 +1738,9 @@
archive
hash
- 0e5b37a03a3f873d15142473b193ec5f
+ 38574fbcb6c94c42745ef48748002e58
url
- http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/15259/98463/kdu-7.10.4.513518-windows-513518.tar.bz2
+ http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/55189/512583/kdu-7.10.4.539108-windows-539108.tar.bz2
name
windows
@@ -1738,16 +1750,16 @@
archive
hash
- da3b1ea90797b189d80ab5d50fdf05d4
+ 3dfeb869c781a766874f0aedc7d4fcef
url
- http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/15260/98469/kdu-7.10.4.513518-windows64-513518.tar.bz2
+ http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/55188/512576/kdu-7.10.4.539108-windows64-539108.tar.bz2
name
windows64
version
- 7.A.4.513518
+ 7.10.4.539108
libhunspell
@@ -1780,9 +1792,9 @@
archive
hash
- 4b238300cf9c405cdcab18030372832f
+ c327e6d6573fc0a808677de47f08acd9
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/534/1149/libhunspell-1.3.2.500526-darwin64-500526.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54844/510092/libhunspell-1.3.2.538974-darwin64-538974.tar.bz2
name
darwin64
@@ -1816,9 +1828,9 @@
archive
hash
- a2025f748a6311ab390f89068b22c702
+ ec22ec25160bcfd2a74f1c7bc8ff6133
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/535/1152/libhunspell-1.3.2.500526-windows-500526.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54986/511824/libhunspell-1.3.2.538974-windows-538974.tar.bz2
name
windows
@@ -1828,16 +1840,16 @@
archive
hash
- 233d86906ef88fa331263162a53e29f2
+ f470c6f3f7b0559e95e76467b808de10
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/536/1155/libhunspell-1.3.2.500526-windows64-500526.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54985/511817/libhunspell-1.3.2.538974-windows64-538974.tar.bz2
name
windows64
version
- 1.3.2.500526
+ 1.3.2.538974
libndofdev
@@ -1870,9 +1882,9 @@
archive
hash
- 840bb6219f63a789749f5f6583c44eee
+ bf765dfe0b928ef3c531cd9618fee89b
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/704/1420/libndofdev-0.1.500695-darwin64-500695.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54843/510085/libndofdev-0.1.538973-darwin64-538973.tar.bz2
name
darwin64
@@ -1882,9 +1894,9 @@
archive
hash
- fdbebbbde3b289d93c0c8c294cf859cb
+ 8abb7d216535009f6c0a7e43b0734b1e
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/708/1426/libndofdev-0.1.500695-windows-500695.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54984/511810/libndofdev-0.1.538973-windows-538973.tar.bz2
name
windows
@@ -1894,16 +1906,16 @@
archive
hash
- 15cef2cec6c8d1980011e26249bd4e90
+ 9da7aed5a914174dcb2be12ecd4a656f
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/707/1423/libndofdev-0.1.500695-windows64-500695.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54983/511803/libndofdev-0.1.538973-windows64-538973.tar.bz2
name
windows64
version
- 0.1.500695
+ 0.1.538973
libpng
@@ -1936,9 +1948,9 @@
archive
hash
- 537b59a75709bd9abe0abe0c7309add4
+ 0932b19bb6a8e2641706afd13d92951d
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/883/1951/libpng-1.6.8.500873-darwin64-500873.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/56313/526740/libpng-1.6.8.539868-darwin64-539868.tar.bz2
name
darwin64
@@ -1972,9 +1984,9 @@
archive
hash
- 9c2950f9d16566979dcd6ca6336778b3
+ f498782698428888113b64a7505c8f7f
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/885/1957/libpng-1.6.8.500873-windows-500873.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/56319/526770/libpng-1.6.8.539868-windows-539868.tar.bz2
name
windows
@@ -1984,16 +1996,16 @@
archive
hash
- 18fe233471e91d5d3ac6d08a296b79ba
+ f8ac4f690a2925418866bccf6eba3cf4
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/884/1954/libpng-1.6.8.500873-windows64-500873.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/56317/526762/libpng-1.6.8.539868-windows64-539868.tar.bz2
name
windows64
version
- 1.6.8.500873
+ 1.6.8.539868
libuuid
@@ -2068,9 +2080,9 @@
archive
hash
- 89a71a652a5ecd7cf6142ff56f40f018
+ 0706b9c3889d767af9f5105d9ffa9b51
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/891/1973/libxml2-2.9.4.500877-darwin64-500877.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/56327/526819/libxml2-2.9.4.539866-darwin64-539866.tar.bz2
name
darwin64
@@ -2104,9 +2116,9 @@
archive
hash
- c2461ba7629c4cef5af623464aded3c6
+ 1b7b979a8387fbb0f278dc681558b9ef
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/888/1960/libxml2-2.9.4.500877-windows-500877.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/56316/526755/libxml2-2.9.4.539866-windows-539866.tar.bz2
name
windows
@@ -2116,16 +2128,16 @@
archive
hash
- 8ec25000f5d72e26c2e7555c73898fbb
+ 4f8ff97d6a9ab350306b62eec8adc810
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/889/1963/libxml2-2.9.4.500877-windows64-500877.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/56314/526748/libxml2-2.9.4.539866-windows64-539866.tar.bz2
name
windows64
version
- 2.9.4.500877
+ 2.9.4.539866
llappearance_utility
@@ -2175,16 +2187,16 @@
archive
hash
- 25db349f18fc1e79f76ab3ba2c8d1330
+ 8501cbaa7e0f254614694da784a9c61c
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/62723/591962/llca-202007011528.544371-common-544371.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/64944/606925/llca-202008010216.546021-common-546021.tar.bz2
name
common
version
- 202007011528.544371
+ 202008010216.546021
llphysicsextensions_source
@@ -2203,9 +2215,9 @@
archive
hash
- 162a3fc9b66626072ec8679361b174f5
+ 14fac452271ebfba37ba5ddcf5bffa54
url
- http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/4722/14837/llphysicsextensions_source-1.0.504710-darwin64-504710.tar.bz2
+ http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/54842/510078/llphysicsextensions_source-1.0.538972-darwin64-538972.tar.bz2
name
darwin64
@@ -2227,16 +2239,16 @@
archive
hash
- dd85c9e0f5fa3ce483ea183db008c4bc
+ f3c066c1aebed8a6519a3e5ce64b9a3c
url
- http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/4726/14858/llphysicsextensions_source-1.0.504710-windows-504710.tar.bz2
+ http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/54982/511796/llphysicsextensions_source-1.0.538972-windows-538972.tar.bz2
name
windows
version
- 1.0.504710
+ 1.0.538972
llphysicsextensions_stub
@@ -2255,9 +2267,9 @@
archive
hash
- 566aa2c6f5b2f40a8b0bedf90d9c6beb
+ f290b000b31f9e36f2489946cbc99f5e
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/4723/14838/llphysicsextensions_stub-1.0.504712-darwin64-504712.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/59995/563653/llphysicsextensions_stub-1.0.542456-darwin64-542456.tar.bz2
name
darwin64
@@ -2279,16 +2291,80 @@
archive
hash
- d830aca10ea9396557b1e613c2736e49
+ 2e5f1f7046a49d8b0bc295aa878116bc
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/4725/14853/llphysicsextensions_stub-1.0.504712-windows-504712.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/60043/564063/llphysicsextensions_stub-1.0.542456-windows-542456.tar.bz2
name
windows
version
- 1.0.504712
+ 1.0.542456
+
+ llphysicsextensions_tpv
+
+ copyright
+ Copyright (c) 2010, Linden Research, Inc.
+ license
+ internal
+ license_file
+ LICENSES/HavokSublicense.pdf
+ name
+ llphysicsextensions_tpv
+ platforms
+
+ darwin64
+
+ archive
+
+ hash
+ 2aa4ec0d72bbe4b755730f1bf92b39e7
+ url
+ http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/30340/257304/llphysicsextensions_tpv-1.0.542327-darwin64-542327.tar.bz2
+
+ name
+ darwin64
+
+ linux64
+
+ archive
+
+ hash
+ 711f4ec769e4b5f59ba25ee43c11bcbc
+ url
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/4724/14846/llphysicsextensions_stub-1.0.504712-linux64-504712.tar.bz2
+
+ name
+ linux64
+
+ windows
+
+ archive
+
+ hash
+ ad9aba5e2c43a37b6530a0d2de64df1c
+ url
+ http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/30341/257307/llphysicsextensions_tpv-1.0.542327-windows-542327.tar.bz2
+
+ name
+ windows
+
+ windows64
+
+ archive
+
+ hash
+ 46689ff1442a8eccac3a7f3258308e1e
+ url
+ http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/30341/257307/llphysicsextensions_tpv-1.0.542327-windows64-542327.tar.bz2
+
+ name
+ windows
+
+
+ version
+ 1.0.542327
mesa
@@ -2336,9 +2412,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- f51bcd9245ed4e4ca1fa250ba9b112ce
+ 937ce1a2158c0cfff37f5989f5b24aba
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9259/41575/nghttp2-1.25.0.509246-darwin64-509246.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/64066/601156/nghttp2-1.40.0.545354-darwin64-545354.tar.bz2
name
darwin64
@@ -2372,9 +2448,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 8367d6743356ad637e61335ee319f7a7
+ 138b881bdf37dff4e626e022a50dd11f
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9261/41597/nghttp2-1.25.0.509246-windows-509246.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/64069/601181/nghttp2-1.40.0.545354-windows-545354.tar.bz2
name
windows
@@ -2384,9 +2460,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 3267acca5dbfe6b8770deeebd548ee6a
+ c23c6480c7cbea60a2bd26e257adc0a7
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/9260/41591/nghttp2-1.25.0.509246-windows64-509246.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/64068/601177/nghttp2-1.40.0.545354-windows64-545354.tar.bz2
name
windows64
@@ -2395,7 +2471,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
source_type
hg
version
- 1.25.0.509246
+ 1.40.0.545354
nvapi
@@ -2416,9 +2492,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 22c7be12c1d2ee87b059be903d7f2fbd
+ 4305515ad326c911a390388366a9107b
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/p64_3p-nvapi/rev/314405/arch/CYGWIN/installer/nvapi-352.314405-windows-314405.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54947/511704/nvapi-352.539058-windows-539058.tar.bz2
name
windows
@@ -2428,16 +2504,16 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 90e32843a0e21037001dc88240008e1f
+ 25c8ac919f24b8952653d38ec43640e5
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/p64_3p-nvapi/rev/314405/arch/CYGWIN/installer/nvapi-352.314405-windows64-314405.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54945/511697/nvapi-352.539058-windows64-539058.tar.bz2
name
windows64
version
- 352.314405
+ 352.539058
ogg_vorbis
@@ -2470,9 +2546,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 2c17cfd900c88914e06947fe0f1fdae4
+ a066f1d12caee1d87fc72f48169f9677
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/25395/199641/ogg_vorbis-1.3.3-1.3.6.520171-darwin64-520171.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54841/510071/ogg_vorbis-1.3.3-1.3.6.538971-darwin64-538971.tar.bz2
name
darwin64
@@ -2506,9 +2582,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 1818627d4d1f05b49709717e240bdcf4
+ d4b8ed3fd679a2b484d2d1a66c063908
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/25396/199634/ogg_vorbis-1.3.3-1.3.6.520171-windows-520171.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54981/511789/ogg_vorbis-1.3.3-1.3.6.538971-windows-538971.tar.bz2
name
windows
@@ -2518,16 +2594,16 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- d124788c798684c890c1803fca541a10
+ ec4a657fe639bb458ee5132062146a7a
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/25397/199631/ogg_vorbis-1.3.3-1.3.6.520171-windows64-520171.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54980/511782/ogg_vorbis-1.3.3-1.3.6.538971-windows64-538971.tar.bz2
name
windows64
version
- 1.3.3-1.3.6.520171
+ 1.3.3-1.3.6.538971
open-libndofdev
@@ -2660,9 +2736,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- f7013e1f0b6a877090622fd73ec72cbc
+ 5abf2d9c0b250821c59cc60cd94fd8af
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/1114/2576/openjpeg-1.5.1.501102-darwin64-501102.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54840/510064/openjpeg-1.5.1.538970-darwin64-538970.tar.bz2
name
darwin64
@@ -2696,9 +2772,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 8a7f0be5647e07235d205ac00805fb78
+ 222a406ecb4071a9cc9635353afa337e
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/1116/2586/openjpeg-1.5.1.501102-windows-501102.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54977/511775/openjpeg-1.5.1.538970-windows-538970.tar.bz2
name
windows
@@ -2708,16 +2784,16 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 398544058036bc27097fcff208934d11
+ 5b5c80807fa8161f3480be3d89fe9516
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/1115/2581/openjpeg-1.5.1.501102-windows64-501102.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54974/511767/openjpeg-1.5.1.538970-windows64-538970.tar.bz2
name
windows64
version
- 1.5.1.501102
+ 1.5.1.538970
openssl
@@ -2750,9 +2826,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 6c28cce95e3576daf66252b07d9d151f
+ 18aef0c8fc471b6539addbdc019aea25
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/8340/33489/openssl-1.0.2l.508328-darwin64-508328.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/56325/526804/openssl-1.0.2l.539874-darwin64-539874.tar.bz2
name
darwin64
@@ -2786,9 +2862,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- ffdb11a4c7aff72086c01555f931c918
+ 2b2f61313b1cbd2893c1ba5bf15061fa
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/8341/33481/openssl-1.0.2l.508328-windows-508328.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/56328/526826/openssl-1.0.2l.539874-windows-539874.tar.bz2
name
windows
@@ -2798,16 +2874,16 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- d875fc7d1f3a7bd9f85cfde05d9ae3dc
+ 59aae854155bc7119e0dca25e65828c0
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/8342/33480/openssl-1.0.2l.508328-windows64-508328.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/56326/526811/openssl-1.0.2l.539874-windows64-539874.tar.bz2
name
windows64
version
- 1.0.2l.508328
+ 1.0.2l.539874
pcre
@@ -2840,9 +2916,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- addfbc0635b0ea65d7a151dd7ec5ef85
+ d8c0f97fe5abef43e72b6f84aba698b2
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/909/2015/pcre-8.35.500898-darwin64-500898.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54856/510176/pcre-8.35.538986-darwin64-538986.tar.bz2
name
darwin64
@@ -2876,9 +2952,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 150220f39f0aa5a8d9e609b450a9b147
+ 3660db45793df3050b63920bfb7d8479
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/911/2021/pcre-8.35.500898-windows-500898.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55041/512002/pcre-8.35.538986-windows-538986.tar.bz2
name
linux
@@ -2888,16 +2964,16 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- eaebfb4a96a6306ee8e0b18434d125f9
+ cdee8e8b48a66266550bf279c40abc22
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/910/2018/pcre-8.35.500898-windows64-500898.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55038/511992/pcre-8.35.538986-windows64-538986.tar.bz2
name
windows64
version
- 8.35.500898
+ 8.35.538986
slvoice
@@ -2930,9 +3006,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- f824d586ab5de6edd14ef6828e9e4b66
+ 321a8542e7b693fbe8e44ebface06087
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44719/395040/slvoice-4.10.0000.32327.5fc3fe7c.531581-darwin64-531581.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55966/524403/slvoice-4.10.0000.32327.5fc3fe7c.539691-darwin64-539691.tar.bz2
name
darwin64
@@ -2966,9 +3042,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 1941c17c81905f23b4928288bcf719fb
+ fb1a57a1cf5e38a3d51b32307b93ffba
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44720/395047/slvoice-4.10.0000.32327.5fc3fe7c.531581-windows-531581.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55968/524423/slvoice-4.10.0000.32327.5fc3fe7c.539691-windows-539691.tar.bz2
name
windows
@@ -2978,16 +3054,16 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- baa6cdc8e8762d5519996ed9faa0bf3f
+ 81df970eb0c97d415d7bd12049c82042
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44721/395056/slvoice-4.10.0000.32327.5fc3fe7c.531581-windows64-531581.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55967/524409/slvoice-4.10.0000.32327.5fc3fe7c.539691-windows64-539691.tar.bz2
name
windows64
version
- 4.10.0000.32327.5fc3fe7c.531581
+ 4.10.0000.32327.5fc3fe7c.539691
tut
@@ -3008,9 +3084,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 722563bd6e2ae0c7e53c027d267154f7
+ 64e1c979aea2f74fe9c2d9d04573336d
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/496/1054/tut-2008.11.30-common-500403.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55001/511871/tut-2008.11.30-common-539059.tar.bz2
name
common
@@ -3050,9 +3126,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 5b9cd1d6fac519aad59f6d53a54229c5
+ d463360491b6b5cb7a57cd67a90ececb
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/347/872/uriparser-0.8.0.1-darwin64-500342.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54838/510050/uriparser-0.8.0.1-darwin64-538968.tar.bz2
name
darwin64
@@ -3086,9 +3162,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 1becd11c19dd1763f0322ba4d1a5ee06
+ 57a88be57694de6cf9f516125af2c4c9
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/521/1129/uriparser-0.8.0.1-windows-500342.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54963/511746/uriparser-0.8.0.1-windows-538968.tar.bz2
name
windows
@@ -3098,9 +3174,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 587db55a2a3ce57628374b5e27b3272e
+ f39cc91f2a5dad13790ec18269844ae4
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/349/875/uriparser-0.8.0.1-windows64-500342.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54962/511739/uriparser-0.8.0.1-windows64-538968.tar.bz2
name
windows64
@@ -3128,9 +3204,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 5ba21b80695975ab1e1b1c79d0f10c10
+ c5ab9d9d7482e48cd76f4bf391900a8c
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55879/522561/viewer_manager-2.0.539630-darwin64-539630.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/43369/385585/viewer_manager-2.0.531000-darwin64-531000.tar.bz2
name
darwin64
@@ -3152,9 +3228,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 0f788eb745fc062ad737824836e48691
+ 6b10d7407686d9e12e63576256581e3e
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55880/522567/viewer_manager-2.0.539630-windows-539630.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/43370/385592/viewer_manager-2.0.531000-windows-531000.tar.bz2
name
windows
@@ -3165,7 +3241,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
source_type
hg
version
- 2.0.539630
+ 2.0.531000
vlc-bin
@@ -3184,9 +3260,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- e5635e173c75dc0675b48ab5f5e4868b
+ 5e553a4358203f283c74744aed2fcd8c
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/12143/71451/vlc_bin-2.2.8.511703-darwin64-511703.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54836/510036/vlc_bin-2.2.8.538966-darwin64-538966.tar.bz2
name
darwin64
@@ -3208,9 +3284,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- add560654a53cb1c554044a4fac3c718
+ ca84b7c5f86e702fb35727eed8f0c8c4
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/12144/71458/vlc_bin-2.2.8.511703-windows-511703.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54958/511725/vlc_bin-2.2.8.538966-windows-538966.tar.bz2
name
windows
@@ -3220,16 +3296,16 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 94bf04b49acc1e1bf2c06e2232f8a083
+ 93cd88d90cb8aedbed5cd90ff9262409
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/12145/71463/vlc_bin-2.2.8.511703-windows64-511703.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54954/511718/vlc_bin-2.2.8.538966-windows64-538966.tar.bz2
name
windows64
version
- 2.2.8.511703
+ 2.2.8.538966
xmlrpc-epi
@@ -3262,9 +3338,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- b2d31df56a10c634657eed856c8d7895
+ 99ea1808ee9f5b55029daa9fdef86776
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/728/1494/xmlrpc_epi-0.54.1.500719-darwin64-500719.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55063/512104/xmlrpc_epi-0.54.1.539072-darwin64-539072.tar.bz2
name
darwin64
@@ -3298,9 +3374,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 6c16f020bf01155e6746487af0b26173
+ 94643b7cebb449f049fa9e32ae682bcd
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/729/1497/xmlrpc_epi-0.54.1.500719-windows-500719.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55138/512288/xmlrpc_epi-0.54.1.539072-windows-539072.tar.bz2
name
windows
@@ -3310,16 +3386,16 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- a9dda7caa8835c52b3735711cfee4eb9
+ c409de1974a879291ce7daaf52348d85
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/730/1500/xmlrpc_epi-0.54.1.500719-windows64-500719.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55137/512279/xmlrpc_epi-0.54.1.539072-windows64-539072.tar.bz2
name
windows64
version
- 0.54.1.500719
+ 0.54.1.539072
zlib
@@ -3352,9 +3428,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- e204dee29902549f50af1af2bb098df5
+ 9785bda5b4d3b41bf391b33d0da78c9e
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/867/1903/zlib-1.2.8.500857-darwin64-500857.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54858/510190/zlib-1.2.8.538988-darwin64-538988.tar.bz2
name
darwin64
@@ -3390,9 +3466,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- f92cbb0ab5e5d20789bf6102f9a27aa6
+ ebdb07d4aaa5312005a8773f625032a4
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/868/1906/zlib-1.2.8.500857-windows-500857.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55048/512031/zlib-1.2.8.538988-windows-538988.tar.bz2
name
windows
@@ -3402,16 +3478,16 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
hash
- 70a56767f6a109af412838875d0b5f1b
+ 0ac95f3dece7d575ba45cf5728f53eea
url
- http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/869/1909/zlib-1.2.8.500857-windows64-500857.tar.bz2
+ http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/55047/512024/zlib-1.2.8.538988-windows64-538988.tar.bz2
name
windows64
version
- 1.2.8.500857
+ 1.2.8.538988
package_description
@@ -3763,7 +3839,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
windows
build_directory
- build-vc120-$AUTOBUILD_ADDRSIZE
+ build-vc${AUTOBUILD_VSVER|150}-$AUTOBUILD_ADDRSIZE
configurations
RelWithDebInfo
diff --git a/build.sh b/build.sh
index e35028ad6e..545c913f92 100755
--- a/build.sh
+++ b/build.sh
@@ -28,7 +28,7 @@ build_dir_Linux()
build_dir_CYGWIN()
{
- echo build-vc120-${AUTOBUILD_ADDRSIZE}
+ echo build-vc${AUTOBUILD_VSVER:-120}-${AUTOBUILD_ADDRSIZE}
}
viewer_channel_suffix()
diff --git a/doc/contributions.txt b/doc/contributions.txt
index 785e849886..e2b69a9fe4 100755
--- a/doc/contributions.txt
+++ b/doc/contributions.txt
@@ -1090,6 +1090,7 @@ Nicky Dasmijn
SL-11061
SL-11072
SL-13141
+ SL-13642
Nicky Perian
OPEN-1
STORM-1087
diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt
index 62a8f3f003..53e5d7b6a5 100644
--- a/indra/CMakeLists.txt
+++ b/indra/CMakeLists.txt
@@ -15,6 +15,11 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
include(Variables)
include(BuildVersion)
+set(LEGACY_STDIO_LIBS)
+if (WINDOWS)
+ set(LEGACY_STDIO_LIBS legacy_stdio_definitions)
+endif (WINDOWS)
+
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING
"Build type. One of: Debug Release RelWithDebInfo" FORCE)
diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake
index 03da30649a..865c057e33 100644
--- a/indra/cmake/00-Common.cmake
+++ b/indra/cmake/00-Common.cmake
@@ -60,7 +60,10 @@ if (WINDOWS)
# http://www.cmake.org/pipermail/cmake/2009-September/032143.html
string(REPLACE "/Zm1000" " " CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
+ # Without PreferredToolArchitecture=x64, as of 2020-06-26 the 32-bit
+ # compiler on our TeamCity build hosts has started running out of virtual
+ # memory for the precompiled header file.
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /p:PreferredToolArchitecture=x64")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO
"${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Zo"
diff --git a/indra/cmake/Boost.cmake b/indra/cmake/Boost.cmake
index 180a84dbcf..06a7ab6d75 100644
--- a/indra/cmake/Boost.cmake
+++ b/indra/cmake/Boost.cmake
@@ -8,7 +8,7 @@ if (USESYSTEMLIBS)
include(FindBoost)
set(BOOST_CONTEXT_LIBRARY boost_context-mt)
- set(BOOST_COROUTINE_LIBRARY boost_coroutine-mt)
+ set(BOOST_FIBER_LIBRARY boost_fiber-mt)
set(BOOST_FILESYSTEM_LIBRARY boost_filesystem-mt)
set(BOOST_PROGRAM_OPTIONS_LIBRARY boost_program_options-mt)
set(BOOST_REGEX_LIBRARY boost_regex-mt)
@@ -18,11 +18,15 @@ if (USESYSTEMLIBS)
else (USESYSTEMLIBS)
use_prebuilt_binary(boost)
set(Boost_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include)
- set(BOOST_VERSION "1.55")
+
+ # As of sometime between Boost 1.67 and 1.72, Boost libraries are suffixed
+ # with the address size.
+ set(addrsfx "-x${ADDRESS_SIZE}")
if (WINDOWS)
if(MSVC80)
# This should be obsolete at this point
+ set(BOOST_VERSION "1.55")
set(BOOST_CONTEXT_LIBRARY
optimized libboost_context-vc80-mt-${BOOST_VERSION}
debug libboost_context-vc80-mt-gd-${BOOST_VERSION})
@@ -47,80 +51,80 @@ else (USESYSTEMLIBS)
else(MSVC80)
# MSVC 10.0 config
set(BOOST_CONTEXT_LIBRARY
- optimized libboost_context-mt
- debug libboost_context-mt-gd)
- set(BOOST_COROUTINE_LIBRARY
- optimized libboost_coroutine-mt
- debug libboost_coroutine-mt-gd)
+ optimized libboost_context-mt${addrsfx}
+ debug libboost_context-mt${addrsfx}-gd)
+ set(BOOST_FIBER_LIBRARY
+ optimized libboost_fiber-mt${addrsfx}
+ debug libboost_fiber-mt${addrsfx}-gd)
set(BOOST_FILESYSTEM_LIBRARY
- optimized libboost_filesystem-mt
- debug libboost_filesystem-mt-gd)
+ optimized libboost_filesystem-mt${addrsfx}
+ debug libboost_filesystem-mt${addrsfx}-gd)
set(BOOST_PROGRAM_OPTIONS_LIBRARY
- optimized libboost_program_options-mt
- debug libboost_program_options-mt-gd)
+ optimized libboost_program_options-mt${addrsfx}
+ debug libboost_program_options-mt${addrsfx}-gd)
set(BOOST_REGEX_LIBRARY
- optimized libboost_regex-mt
- debug libboost_regex-mt-gd)
+ optimized libboost_regex-mt${addrsfx}
+ debug libboost_regex-mt${addrsfx}-gd)
set(BOOST_SIGNALS_LIBRARY
- optimized libboost_signals-mt
- debug libboost_signals-mt-gd)
+ optimized libboost_signals-mt${addrsfx}
+ debug libboost_signals-mt${addrsfx}-gd)
set(BOOST_SYSTEM_LIBRARY
- optimized libboost_system-mt
- debug libboost_system-mt-gd)
+ optimized libboost_system-mt${addrsfx}
+ debug libboost_system-mt${addrsfx}-gd)
set(BOOST_THREAD_LIBRARY
- optimized libboost_thread-mt
- debug libboost_thread-mt-gd)
+ optimized libboost_thread-mt${addrsfx}
+ debug libboost_thread-mt${addrsfx}-gd)
endif (MSVC80)
elseif (LINUX)
set(BOOST_CONTEXT_LIBRARY
- optimized boost_context-mt
- debug boost_context-mt-d)
- set(BOOST_COROUTINE_LIBRARY
- optimized boost_coroutine-mt
- debug boost_coroutine-mt-d)
+ optimized boost_context-mt${addrsfx}
+ debug boost_context-mt${addrsfx}-d)
+ set(BOOST_FIBER_LIBRARY
+ optimized boost_fiber-mt${addrsfx}
+ debug boost_fiber-mt${addrsfx}-d)
set(BOOST_FILESYSTEM_LIBRARY
- optimized boost_filesystem-mt
- debug boost_filesystem-mt-d)
+ optimized boost_filesystem-mt${addrsfx}
+ debug boost_filesystem-mt${addrsfx}-d)
set(BOOST_PROGRAM_OPTIONS_LIBRARY
- optimized boost_program_options-mt
- debug boost_program_options-mt-d)
+ optimized boost_program_options-mt${addrsfx}
+ debug boost_program_options-mt${addrsfx}-d)
set(BOOST_REGEX_LIBRARY
- optimized boost_regex-mt
- debug boost_regex-mt-d)
+ optimized boost_regex-mt${addrsfx}
+ debug boost_regex-mt${addrsfx}-d)
set(BOOST_SIGNALS_LIBRARY
- optimized boost_signals-mt
- debug boost_signals-mt-d)
+ optimized boost_signals-mt${addrsfx}
+ debug boost_signals-mt${addrsfx}-d)
set(BOOST_SYSTEM_LIBRARY
- optimized boost_system-mt
- debug boost_system-mt-d)
+ optimized boost_system-mt${addrsfx}
+ debug boost_system-mt${addrsfx}-d)
set(BOOST_THREAD_LIBRARY
- optimized boost_thread-mt
- debug boost_thread-mt-d)
+ optimized boost_thread-mt${addrsfx}
+ debug boost_thread-mt${addrsfx}-d)
elseif (DARWIN)
set(BOOST_CONTEXT_LIBRARY
- optimized boost_context-mt
- debug boost_context-mt-d)
- set(BOOST_COROUTINE_LIBRARY
- optimized boost_coroutine-mt
- debug boost_coroutine-mt-d)
+ optimized boost_context-mt${addrsfx}
+ debug boost_context-mt${addrsfx}-d)
+ set(BOOST_FIBER_LIBRARY
+ optimized boost_fiber-mt${addrsfx}
+ debug boost_fiber-mt${addrsfx}-d)
set(BOOST_FILESYSTEM_LIBRARY
- optimized boost_filesystem-mt
- debug boost_filesystem-mt-d)
+ optimized boost_filesystem-mt${addrsfx}
+ debug boost_filesystem-mt${addrsfx}-d)
set(BOOST_PROGRAM_OPTIONS_LIBRARY
- optimized boost_program_options-mt
- debug boost_program_options-mt-d)
+ optimized boost_program_options-mt${addrsfx}
+ debug boost_program_options-mt${addrsfx}-d)
set(BOOST_REGEX_LIBRARY
- optimized boost_regex-mt
- debug boost_regex-mt-d)
+ optimized boost_regex-mt${addrsfx}
+ debug boost_regex-mt${addrsfx}-d)
set(BOOST_SIGNALS_LIBRARY
- optimized boost_signals-mt
- debug boost_signals-mt-d)
+ optimized boost_signals-mt${addrsfx}
+ debug boost_signals-mt${addrsfx}-d)
set(BOOST_SYSTEM_LIBRARY
- optimized boost_system-mt
- debug boost_system-mt-d)
+ optimized boost_system-mt${addrsfx}
+ debug boost_system-mt${addrsfx}-d)
set(BOOST_THREAD_LIBRARY
- optimized boost_thread-mt
- debug boost_thread-mt-d)
+ optimized boost_thread-mt${addrsfx}
+ debug boost_thread-mt${addrsfx}-d)
endif (WINDOWS)
endif (USESYSTEMLIBS)
diff --git a/indra/cmake/BuildPackagesInfo.cmake b/indra/cmake/BuildPackagesInfo.cmake
index 4314cca33d..8f8b6b2330 100644
--- a/indra/cmake/BuildPackagesInfo.cmake
+++ b/indra/cmake/BuildPackagesInfo.cmake
@@ -1,6 +1,7 @@
# -*- cmake -*-
# Construct the version and copyright information based on package data.
include(Python)
+include(FindAutobuild)
# packages-formatter.py runs autobuild install --versions, which needs to know
# the build_directory, which (on Windows) depends on AUTOBUILD_ADDRSIZE.
@@ -13,7 +14,7 @@ add_custom_command(OUTPUT packages-info.txt
DEPENDS ${CMAKE_SOURCE_DIR}/../scripts/packages-formatter.py
${CMAKE_SOURCE_DIR}/../autobuild.xml
COMMAND ${PYTHON_EXECUTABLE}
- ${CMAKE_SOURCE_DIR}/cmake/run_build_test.py -DAUTOBUILD_ADDRSIZE=${ADDRESS_SIZE}
+ ${CMAKE_SOURCE_DIR}/cmake/run_build_test.py -DAUTOBUILD_ADDRSIZE=${ADDRESS_SIZE} -DAUTOBUILD=${AUTOBUILD_EXECUTABLE}
${PYTHON_EXECUTABLE}
${CMAKE_SOURCE_DIR}/../scripts/packages-formatter.py "${VIEWER_CHANNEL}" "${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}" > packages-info.txt
)
diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt
index 3a14bf522f..a17e37cd32 100644
--- a/indra/cmake/CMakeLists.txt
+++ b/indra/cmake/CMakeLists.txt
@@ -22,7 +22,6 @@ set(cmake_SOURCE_FILES
Copy3rdPartyLibs.cmake
DBusGlib.cmake
DeploySharedLibs.cmake
- DirectX.cmake
DragDrop.cmake
EXPAT.cmake
FindAPR.cmake
diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake
index aeadfdd626..8efad33f71 100644
--- a/indra/cmake/Copy3rdPartyLibs.cmake
+++ b/indra/cmake/Copy3rdPartyLibs.cmake
@@ -7,6 +7,21 @@
include(CMakeCopyIfDifferent)
include(Linking)
+# When we copy our dependent libraries, we almost always want to copy them to
+# both the Release and the RelWithDebInfo staging directories. This has
+# resulted in duplicate (or worse, erroneous attempted duplicate)
+# copy_if_different commands. Encapsulate that usage.
+# Pass FROM_DIR, TARGETS and the files to copy. TO_DIR is implicit.
+# to_staging_dirs diverges from copy_if_different in that it appends to TARGETS.
+MACRO(to_staging_dirs from_dir targets)
+ foreach(staging_dir
+ "${SHARED_LIB_STAGING_DIR_RELEASE}"
+ "${SHARED_LIB_STAGING_DIR_RELWITHDEBINFO}")
+ copy_if_different("${from_dir}" "${staging_dir}" out_targets ${ARGN})
+ list(APPEND "${targets}" "${out_targets}")
+ endforeach()
+ENDMACRO(to_staging_dirs from_dir to_dir targets)
+
###################################################################
# set up platform specific lists of files that need to be copied
###################################################################
@@ -73,95 +88,54 @@ if(WINDOWS)
#*******************************
# Copy MS C runtime dlls, required for packaging.
- # *TODO - Adapt this to support VC9
if (MSVC80)
- list(APPEND LMSVC_VER 80)
- list(APPEND LMSVC_VERDOT 8.0)
+ set(MSVC_VER 80)
elseif (MSVC_VERSION EQUAL 1600) # VisualStudio 2010
MESSAGE(STATUS "MSVC_VERSION ${MSVC_VERSION}")
elseif (MSVC_VERSION EQUAL 1800) # VisualStudio 2013, which is (sigh) VS 12
- list(APPEND LMSVC_VER 120)
- list(APPEND LMSVC_VERDOT 12.0)
+ set(MSVC_VER 120)
+ elseif (MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1920) # Visual Studio 2017
+ set(MSVC_VER 140)
else (MSVC80)
MESSAGE(WARNING "New MSVC_VERSION ${MSVC_VERSION} of MSVC: adapt Copy3rdPartyLibs.cmake")
endif (MSVC80)
- # try to copy VS2010 redist independently of system version
- # maint-7360 CP
- # list(APPEND LMSVC_VER 100)
- # list(APPEND LMSVC_VERDOT 10.0)
-
- list(LENGTH LMSVC_VER count)
- math(EXPR count "${count}-1")
- foreach(i RANGE ${count})
- list(GET LMSVC_VER ${i} MSVC_VER)
- list(GET LMSVC_VERDOT ${i} MSVC_VERDOT)
- MESSAGE(STATUS "Copying redist libs for VC ${MSVC_VERDOT}")
- FIND_PATH(debug_msvc_redist_path NAME msvcr${MSVC_VER}d.dll
- PATHS
- [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\${MSVC_VERDOT}\\Setup\\VC;ProductDir]/redist/Debug_NonRedist/x86/Microsoft.VC${MSVC_VER}.DebugCRT
- [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Windows;Directory]/SysWOW64
- [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Windows;Directory]/System32
- ${MSVC_DEBUG_REDIST_PATH}
- NO_DEFAULT_PATH
+ if(ADDRESS_SIZE EQUAL 32)
+ # this folder contains the 32bit DLLs.. (yes really!)
+ set(registry_find_path "[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Windows;Directory]/SysWOW64")
+ else(ADDRESS_SIZE EQUAL 32)
+ # this folder contains the 64bit DLLs.. (yes really!)
+ set(registry_find_path "[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Windows;Directory]/System32")
+ endif(ADDRESS_SIZE EQUAL 32)
+
+ # Having a string containing the system registry path is a start, but to
+ # get CMake to actually read the registry, we must engage some other
+ # operation.
+ get_filename_component(registry_path "${registry_find_path}" ABSOLUTE)
+
+ # These are candidate DLL names. Empirically, VS versions before 2015 have
+ # msvcp*.dll and msvcr*.dll. VS 2017 has msvcp*.dll and vcruntime*.dll.
+ # Check each of them.
+ foreach(release_msvc_file
+ msvcp${MSVC_VER}.dll
+ msvcr${MSVC_VER}.dll
+ vcruntime${MSVC_VER}.dll
)
-
- if(EXISTS ${debug_msvc_redist_path})
- set(debug_msvc_files
- msvcr${MSVC_VER}d.dll
- msvcp${MSVC_VER}d.dll
- )
-
- copy_if_different(
- ${debug_msvc_redist_path}
- "${SHARED_LIB_STAGING_DIR_DEBUG}"
- out_targets
- ${debug_msvc_files}
- )
- set(third_party_targets ${third_party_targets} ${out_targets})
-
- unset(debug_msvc_redist_path CACHE)
- endif()
-
- if(ADDRESS_SIZE EQUAL 32)
- # this folder contains the 32bit DLLs.. (yes really!)
- set(registry_find_path "[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Windows;Directory]/SysWOW64")
- else(ADDRESS_SIZE EQUAL 32)
- # this folder contains the 64bit DLLs.. (yes really!)
- set(registry_find_path "[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Windows;Directory]/System32")
- endif(ADDRESS_SIZE EQUAL 32)
-
- FIND_PATH(release_msvc_redist_path NAME msvcr${MSVC_VER}.dll
- PATHS
- ${registry_find_path}
- NO_DEFAULT_PATH
- )
-
- if(EXISTS ${release_msvc_redist_path})
- set(release_msvc_files
- msvcr${MSVC_VER}.dll
- msvcp${MSVC_VER}.dll
- )
-
- copy_if_different(
- ${release_msvc_redist_path}
- "${SHARED_LIB_STAGING_DIR_RELEASE}"
- out_targets
- ${release_msvc_files}
- )
- set(third_party_targets ${third_party_targets} ${out_targets})
-
- copy_if_different(
- ${release_msvc_redist_path}
- "${SHARED_LIB_STAGING_DIR_RELWITHDEBINFO}"
- out_targets
- ${release_msvc_files}
- )
- set(third_party_targets ${third_party_targets} ${out_targets})
-
- unset(release_msvc_redist_path CACHE)
+ if(EXISTS "${registry_path}/${release_msvc_file}")
+ to_staging_dirs(
+ ${registry_path}
+ third_party_targets
+ ${release_msvc_file})
+ else()
+ # This isn't a WARNING because, as noted above, every VS version
+ # we've observed has only a subset of the specified DLL names.
+ MESSAGE(STATUS "Redist lib ${release_msvc_file} not found")
endif()
endforeach()
+ MESSAGE(STATUS "Will copy redist files for MSVC ${MSVC_VER}:")
+ foreach(target ${third_party_targets})
+ MESSAGE(STATUS "${target}")
+ endforeach()
elseif(DARWIN)
set(SHARED_LIB_STAGING_DIR_DEBUG "${SHARED_LIB_STAGING_DIR}/Debug/Resources")
@@ -186,10 +160,11 @@ elseif(DARWIN)
libexception_handler.dylib
${EXPAT_COPY}
libGLOD.dylib
+ libhunspell-1.3.0.dylib
libndofdev.dylib
libnghttp2.dylib
libnghttp2.14.dylib
- libnghttp2.14.14.0.dylib
+ libnghttp2.14.19.0.dylib
)
if (FMODSTUDIO)
@@ -272,52 +247,28 @@ endif(WINDOWS)
# Done building the file lists, now set up the copy commands.
################################################################
-copy_if_different(
- ${vivox_lib_dir}
- "${SHARED_LIB_STAGING_DIR_DEBUG}"
- out_targets
- ${vivox_libs}
- )
-set(third_party_targets ${third_party_targets} ${out_targets})
-
+# Curiously, slvoice_files are only copied to SHARED_LIB_STAGING_DIR_RELEASE.
+# It's unclear whether this is oversight or intentional, but anyway leave the
+# single copy_if_different command rather than using to_staging_dirs.
copy_if_different(
${slvoice_src_dir}
"${SHARED_LIB_STAGING_DIR_RELEASE}"
out_targets
${slvoice_files}
)
-copy_if_different(
+list(APPEND third_party_targets ${out_targets})
+
+to_staging_dirs(
${vivox_lib_dir}
- "${SHARED_LIB_STAGING_DIR_RELEASE}"
- out_targets
+ third_party_targets
${vivox_libs}
)
-set(third_party_targets ${third_party_targets} ${out_targets})
-
-copy_if_different(
- ${vivox_lib_dir}
- "${SHARED_LIB_STAGING_DIR_RELWITHDEBINFO}"
- out_targets
- ${vivox_libs}
- )
-set(third_party_targets ${third_party_targets} ${out_targets})
-
-copy_if_different(
+to_staging_dirs(
${release_src_dir}
- "${SHARED_LIB_STAGING_DIR_RELEASE}"
- out_targets
+ third_party_targets
${release_files}
)
-set(third_party_targets ${third_party_targets} ${out_targets})
-
-copy_if_different(
- ${release_src_dir}
- "${SHARED_LIB_STAGING_DIR_RELWITHDEBINFO}"
- out_targets
- ${release_files}
- )
-set(third_party_targets ${third_party_targets} ${out_targets})
if(NOT USESYSTEMLIBS)
add_custom_target(
diff --git a/indra/cmake/DirectX.cmake b/indra/cmake/DirectX.cmake
deleted file mode 100644
index 1741dc111f..0000000000
--- a/indra/cmake/DirectX.cmake
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- cmake -*-
-
-if (WINDOWS)
- if(ADDRESS_SIZE EQUAL 32)
- set(PROGRAMFILES_x86 $ENV{PROGRAMFILES})
- else(ADDRESS_SIZE EQUAL 32)
- set(PROGRAMFILES_x86 $ENV{PROGRAMFILES\(X86\)})
- endif(ADDRESS_SIZE EQUAL 32)
-
- find_path(DIRECTX_INCLUDE_DIR dxdiag.h
- "$ENV{DXSDK_DIR}/Include"
- "${PROGRAMFILES_x86}/Windows Kits/8.1/Include/um"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2010)/Include"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2009)/Include"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2009)/Include"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2008)/Include"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2008)/Include"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2008)/Include"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (November 2007)/Include"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2007)/Include"
- "C:/DX90SDK/Include"
- "$ENV{PROGRAMFILES}/DX90SDK/Include"
- )
- if (DIRECTX_INCLUDE_DIR)
- include_directories(${DIRECTX_INCLUDE_DIR})
- if (DIRECTX_FIND_QUIETLY)
- message(STATUS "Found DirectX include: ${DIRECTX_INCLUDE_DIR}")
- endif (DIRECTX_FIND_QUIETLY)
- else (DIRECTX_INCLUDE_DIR)
- message(FATAL_ERROR "Could not find DirectX SDK Include")
- endif (DIRECTX_INCLUDE_DIR)
-
-
- find_path(DIRECTX_LIBRARY_DIR dxguid.lib
- "$ENV{DXSDK_DIR}/Lib/x86"
- "${PROGRAMFILES_x86}/Windows Kits/8.1/Lib/winv6.3/um/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2010)/Lib/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2009)/Lib/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2009)/Lib/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2008)/Lib/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2008)/Lib/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2008)/Lib/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (November 2007)/Lib/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2007)/Lib/x86"
- "C:/DX90SDK/Lib"
- "$ENV{PROGRAMFILES}/DX90SDK/Lib"
- )
- if (DIRECTX_LIBRARY_DIR)
- if (DIRECTX_FIND_QUIETLY)
- message(STATUS "Found DirectX include: ${DIRECTX_LIBRARY_DIR}")
- endif (DIRECTX_FIND_QUIETLY)
- else (DIRECTX_LIBRARY_DIR)
- message(FATAL_ERROR "Could not find DirectX SDK Libraries")
- endif (DIRECTX_LIBRARY_DIR)
-
-endif (WINDOWS)
diff --git a/indra/cmake/LLAddBuildTest.cmake b/indra/cmake/LLAddBuildTest.cmake
index b3f42c1a5e..4932e9044f 100644
--- a/indra/cmake/LLAddBuildTest.cmake
+++ b/indra/cmake/LLAddBuildTest.cmake
@@ -53,7 +53,7 @@ INCLUDE(GoogleMock)
${GOOGLEMOCK_INCLUDE_DIRS}
)
SET(alltest_LIBRARIES
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
${GOOGLEMOCK_LIBRARIES}
@@ -200,8 +200,9 @@ FUNCTION(LL_ADD_INTEGRATION_TEST
)
SET(libraries
+ ${LEGACY_STDIO_LIBS}
${library_dependencies}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
${GOOGLEMOCK_LIBRARIES}
diff --git a/indra/cmake/LLAppearance.cmake b/indra/cmake/LLAppearance.cmake
index ae265d07e3..675330ec72 100644
--- a/indra/cmake/LLAppearance.cmake
+++ b/indra/cmake/LLAppearance.cmake
@@ -18,7 +18,7 @@ endif (BUILD_HEADLESS)
set(LLAPPEARANCE_LIBRARIES llappearance
llmessage
llcorehttp
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
)
diff --git a/indra/cmake/LLCommon.cmake b/indra/cmake/LLCommon.cmake
index 3e29297c58..8900419f9b 100644
--- a/indra/cmake/LLCommon.cmake
+++ b/indra/cmake/LLCommon.cmake
@@ -19,7 +19,7 @@ if (LINUX)
# specify all libraries that llcommon uses.
# llcommon uses `clock_gettime' which is provided by librt on linux.
set(LLCOMMON_LIBRARIES llcommon
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_THREAD_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
@@ -27,7 +27,7 @@ if (LINUX)
)
else (LINUX)
set(LLCOMMON_LIBRARIES llcommon
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_THREAD_LIBRARY}
${BOOST_SYSTEM_LIBRARY} )
diff --git a/indra/cmake/LLCoreHttp.cmake b/indra/cmake/LLCoreHttp.cmake
index 379ae207de..613453ab5d 100644
--- a/indra/cmake/LLCoreHttp.cmake
+++ b/indra/cmake/LLCoreHttp.cmake
@@ -12,6 +12,6 @@ set(LLCOREHTTP_INCLUDE_DIRS
)
set(LLCOREHTTP_LIBRARIES llcorehttp
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY})
diff --git a/indra/cmake/run_build_test.py b/indra/cmake/run_build_test.py
index 210e43b232..ec5d33f902 100755
--- a/indra/cmake/run_build_test.py
+++ b/indra/cmake/run_build_test.py
@@ -87,7 +87,6 @@ def main(command, arguments=[], libpath=[], vars={}):
# might not exist; instead of KeyError, just use an empty string.
dirs = os.environ.get(var, "").split(os.pathsep)
# Append the sequence in libpath
- log.info("%s += %r" % (var, libpath))
for dir in libpath:
# append system paths at the end
if dir in ('/lib', '/usr/lib'):
@@ -105,16 +104,21 @@ def main(command, arguments=[], libpath=[], vars={}):
# Now rebuild the path string. This way we use a minimum of separators
# -- and we avoid adding a pointless separator when libpath is empty.
os.environ[var] = os.pathsep.join(clean_dirs)
- log.info("%s = %r" % (var, os.environ[var]))
+ # This output format is intended to make it straightforward to copy
+ # the variable settings and the command itself from the build output
+ # and paste the whole thing at a command prompt to rerun it manually.
+ log.info("%s='%s' \\" % (var, os.environ[var]))
# Now handle arbitrary environment variables. The tricky part is ensuring
# that all the keys and values we try to pass are actually strings.
if vars:
- log.info("Setting: %s" % ("\n".join(["%s=%s" % (key, value) for key, value in vars.iteritems()])))
+ for key, value in vars.items():
+ # As noted a few lines above, facilitate copy-paste rerunning.
+ log.info("%s='%s' \\" % (key, value))
os.environ.update(dict([(str(key), str(value)) for key, value in vars.iteritems()]))
# Run the child process.
command_list = [command]
command_list.extend(arguments)
- log.info("Running: %s" % " ".join(command_list))
+ log.info(" ".join((("'%s'" % w) if ' ' in w else w) for w in command_list))
# Make sure we see all relevant output *before* child-process output.
sys.stdout.flush()
try:
@@ -305,8 +309,11 @@ def get_windows_table():
return _windows_table
-log=logging.getLogger(__name__)
-logging.basicConfig()
+# Use this instead of logging.basicConfig() because the latter prefixes
+# every line of output with INFO:__main__:...
+log=logging.getLogger()
+log.setLevel(logging.INFO)
+log.addHandler(logging.StreamHandler())
if __name__ == "__main__":
import argparse
diff --git a/indra/edit-me-to-trigger-new-build.txt b/indra/edit-me-to-trigger-new-build.txt
index fd40910d9e..e69de29bb2 100644
--- a/indra/edit-me-to-trigger-new-build.txt
+++ b/indra/edit-me-to-trigger-new-build.txt
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/indra/integration_tests/llimage_libtest/CMakeLists.txt b/indra/integration_tests/llimage_libtest/CMakeLists.txt
index d9353f904c..5787d4d600 100644
--- a/indra/integration_tests/llimage_libtest/CMakeLists.txt
+++ b/indra/integration_tests/llimage_libtest/CMakeLists.txt
@@ -64,6 +64,7 @@ endif (DARWIN)
# Libraries on which this application depends on
# Sort by high-level to low-level
target_link_libraries(llimage_libtest
+ ${LEGACY_STDIO_LIBS}
${LLCOMMON_LIBRARIES}
${LLVFS_LIBRARIES}
${LLMATH_LIBRARIES}
diff --git a/indra/integration_tests/llui_libtest/CMakeLists.txt b/indra/integration_tests/llui_libtest/CMakeLists.txt
index 34e34c7e47..1cec660eb0 100644
--- a/indra/integration_tests/llui_libtest/CMakeLists.txt
+++ b/indra/integration_tests/llui_libtest/CMakeLists.txt
@@ -75,6 +75,7 @@ endif (DARWIN)
# Libraries on which this library depends, needed for Linux builds
# Sort by high-level to low-level
target_link_libraries(llui_libtest
+ ${LEGACY_STDIO_LIBS}
llui
llinventory
llmessage
diff --git a/indra/linux_crash_logger/CMakeLists.txt b/indra/linux_crash_logger/CMakeLists.txt
index 315aed8d11..d789c850a0 100644
--- a/indra/linux_crash_logger/CMakeLists.txt
+++ b/indra/linux_crash_logger/CMakeLists.txt
@@ -69,7 +69,7 @@ target_link_libraries(linux-crash-logger
${LLMATH_LIBRARIES}
${LLCOREHTTP_LIBRARIES}
${LLCOMMON_LIBRARIES}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${UI_LIBRARIES}
${DB_LIBRARIES}
diff --git a/indra/llappearance/llwearabletype.cpp b/indra/llappearance/llwearabletype.cpp
index d6ff28e2d2..281060d01d 100644
--- a/indra/llappearance/llwearabletype.cpp
+++ b/indra/llappearance/llwearabletype.cpp
@@ -32,7 +32,8 @@
struct WearableEntry : public LLDictionaryEntry
{
- WearableEntry(const std::string &name,
+ WearableEntry(LLWearableType& wtype,
+ const std::string &name,
const std::string& default_new_name,
LLAssetType::EType assetType,
LLInventoryType::EIconName iconName,
@@ -41,7 +42,7 @@ struct WearableEntry : public LLDictionaryEntry
LLDictionaryEntry(name),
mAssetType(assetType),
mDefaultNewName(default_new_name),
- mLabel(LLWearableType::getInstance()->mTrans->getString(name)),
+ mLabel(wtype.mTrans->getString(name)),
mIconName(iconName),
mDisableCameraSwitch(disable_camera_switch),
mAllowMultiwear(allow_multiwear)
@@ -56,41 +57,35 @@ struct WearableEntry : public LLDictionaryEntry
BOOL mAllowMultiwear;
};
-class LLWearableDictionary : public LLSingleton,
+class LLWearableDictionary : public LLParamSingleton,
public LLDictionary
{
- LLSINGLETON(LLWearableDictionary);
+ LLSINGLETON(LLWearableDictionary, LLWearableType&);
};
-LLWearableDictionary::LLWearableDictionary()
+LLWearableDictionary::LLWearableDictionary(LLWearableType& wtype)
{
- if (!LLWearableType::instanceExists())
- {
- // LLWearableType is effectively a wrapper around LLWearableDictionary and is used as storage for LLTranslationBridge
- // Todo: consider merging LLWearableType and LLWearableDictionary
- LL_WARNS() << "Initing LLWearableDictionary without LLWearableType" << LL_ENDL;
- }
- addEntry(LLWearableType::WT_SHAPE, new WearableEntry("shape", "New Shape", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_SHAPE, FALSE, FALSE));
- addEntry(LLWearableType::WT_SKIN, new WearableEntry("skin", "New Skin", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_SKIN, FALSE, FALSE));
- addEntry(LLWearableType::WT_HAIR, new WearableEntry("hair", "New Hair", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_HAIR, FALSE, FALSE));
- addEntry(LLWearableType::WT_EYES, new WearableEntry("eyes", "New Eyes", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_EYES, FALSE, FALSE));
- addEntry(LLWearableType::WT_SHIRT, new WearableEntry("shirt", "New Shirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SHIRT, FALSE, TRUE));
- addEntry(LLWearableType::WT_PANTS, new WearableEntry("pants", "New Pants", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_PANTS, FALSE, TRUE));
- addEntry(LLWearableType::WT_SHOES, new WearableEntry("shoes", "New Shoes", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SHOES, FALSE, TRUE));
- addEntry(LLWearableType::WT_SOCKS, new WearableEntry("socks", "New Socks", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SOCKS, FALSE, TRUE));
- addEntry(LLWearableType::WT_JACKET, new WearableEntry("jacket", "New Jacket", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_JACKET, FALSE, TRUE));
- addEntry(LLWearableType::WT_GLOVES, new WearableEntry("gloves", "New Gloves", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_GLOVES, FALSE, TRUE));
- addEntry(LLWearableType::WT_UNDERSHIRT, new WearableEntry("undershirt", "New Undershirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNDERSHIRT, FALSE, TRUE));
- addEntry(LLWearableType::WT_UNDERPANTS, new WearableEntry("underpants", "New Underpants", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNDERPANTS, FALSE, TRUE));
- addEntry(LLWearableType::WT_SKIRT, new WearableEntry("skirt", "New Skirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SKIRT, FALSE, TRUE));
- addEntry(LLWearableType::WT_ALPHA, new WearableEntry("alpha", "New Alpha", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_ALPHA, FALSE, TRUE));
- addEntry(LLWearableType::WT_TATTOO, new WearableEntry("tattoo", "New Tattoo", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_TATTOO, FALSE, TRUE));
- addEntry(LLWearableType::WT_UNIVERSAL, new WearableEntry("universal", "New Universal", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNIVERSAL, FALSE, TRUE));
+ addEntry(LLWearableType::WT_SHAPE, new WearableEntry(wtype, "shape", "New Shape", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_SHAPE, FALSE, FALSE));
+ addEntry(LLWearableType::WT_SKIN, new WearableEntry(wtype, "skin", "New Skin", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_SKIN, FALSE, FALSE));
+ addEntry(LLWearableType::WT_HAIR, new WearableEntry(wtype, "hair", "New Hair", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_HAIR, FALSE, FALSE));
+ addEntry(LLWearableType::WT_EYES, new WearableEntry(wtype, "eyes", "New Eyes", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_EYES, FALSE, FALSE));
+ addEntry(LLWearableType::WT_SHIRT, new WearableEntry(wtype, "shirt", "New Shirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SHIRT, FALSE, TRUE));
+ addEntry(LLWearableType::WT_PANTS, new WearableEntry(wtype, "pants", "New Pants", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_PANTS, FALSE, TRUE));
+ addEntry(LLWearableType::WT_SHOES, new WearableEntry(wtype, "shoes", "New Shoes", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SHOES, FALSE, TRUE));
+ addEntry(LLWearableType::WT_SOCKS, new WearableEntry(wtype, "socks", "New Socks", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SOCKS, FALSE, TRUE));
+ addEntry(LLWearableType::WT_JACKET, new WearableEntry(wtype, "jacket", "New Jacket", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_JACKET, FALSE, TRUE));
+ addEntry(LLWearableType::WT_GLOVES, new WearableEntry(wtype, "gloves", "New Gloves", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_GLOVES, FALSE, TRUE));
+ addEntry(LLWearableType::WT_UNDERSHIRT, new WearableEntry(wtype, "undershirt", "New Undershirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNDERSHIRT, FALSE, TRUE));
+ addEntry(LLWearableType::WT_UNDERPANTS, new WearableEntry(wtype, "underpants", "New Underpants", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNDERPANTS, FALSE, TRUE));
+ addEntry(LLWearableType::WT_SKIRT, new WearableEntry(wtype, "skirt", "New Skirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SKIRT, FALSE, TRUE));
+ addEntry(LLWearableType::WT_ALPHA, new WearableEntry(wtype, "alpha", "New Alpha", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_ALPHA, FALSE, TRUE));
+ addEntry(LLWearableType::WT_TATTOO, new WearableEntry(wtype, "tattoo", "New Tattoo", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_TATTOO, FALSE, TRUE));
+ addEntry(LLWearableType::WT_UNIVERSAL, new WearableEntry(wtype, "universal", "New Universal", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNIVERSAL, FALSE, TRUE));
- addEntry(LLWearableType::WT_PHYSICS, new WearableEntry("physics", "New Physics", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_PHYSICS, TRUE, TRUE));
+ addEntry(LLWearableType::WT_PHYSICS, new WearableEntry(wtype, "physics", "New Physics", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_PHYSICS, TRUE, TRUE));
- addEntry(LLWearableType::WT_INVALID, new WearableEntry("invalid", "Invalid Wearable", LLAssetType::AT_NONE, LLInventoryType::ICONNAME_UNKNOWN, FALSE, FALSE));
- addEntry(LLWearableType::WT_NONE, new WearableEntry("none", "Invalid Wearable", LLAssetType::AT_NONE, LLInventoryType::ICONNAME_NONE, FALSE, FALSE));
+ addEntry(LLWearableType::WT_INVALID, new WearableEntry(wtype, "invalid", "Invalid Wearable", LLAssetType::AT_NONE, LLInventoryType::ICONNAME_UNKNOWN, FALSE, FALSE));
+ addEntry(LLWearableType::WT_NONE, new WearableEntry(wtype, "none", "Invalid Wearable", LLAssetType::AT_NONE, LLInventoryType::ICONNAME_NONE, FALSE, FALSE));
}
@@ -107,6 +102,14 @@ LLWearableType::~LLWearableType()
delete mTrans;
}
+void LLWearableType::initSingleton()
+{
+ // To make sure all wrapping functions will crash without initing LLWearableType;
+ LLWearableDictionary::initParamSingleton(*this);
+
+ // Todo: consider merging LLWearableType and LLWearableDictionary
+}
+
// static
LLWearableType::EType LLWearableType::typeNameToType(const std::string& type_name)
{
diff --git a/indra/llappearance/llwearabletype.h b/indra/llappearance/llwearabletype.h
index 148ccafdd8..57f3ef160d 100644
--- a/indra/llappearance/llwearabletype.h
+++ b/indra/llappearance/llwearabletype.h
@@ -37,6 +37,7 @@ class LLWearableType : public LLParamSingleton
{
LLSINGLETON(LLWearableType, LLTranslationBridge* trans);
~LLWearableType();
+ void initSingleton();
friend struct WearableEntry;
public:
enum EType
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index af41b9e460..eeb315ead6 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -1,4 +1,3 @@
-
# -*- cmake -*-
project(llcommon)
@@ -44,7 +43,6 @@ set(llcommon_SOURCE_FILES
llcleanup.cpp
llcommon.cpp
llcommonutils.cpp
- llcoro_get_id.cpp
llcoros.cpp
llcrc.cpp
llcriticaldamp.cpp
@@ -106,6 +104,7 @@ set(llcommon_SOURCE_FILES
llstring.cpp
llstringtable.cpp
llsys.cpp
+ lltempredirect.cpp
llthread.cpp
llthreadlocalstorage.cpp
llthreadsafequeue.cpp
@@ -146,7 +145,7 @@ set(llcommon_HEADER_FILES
llcleanup.h
llcommon.h
llcommonutils.h
- llcoro_get_id.h
+ llcond.h
llcoros.h
llcrc.h
llcriticaldamp.h
@@ -186,9 +185,9 @@ set(llcommon_HEADER_FILES
llkeythrottle.h
llleap.h
llleaplistener.h
- lllistenerwrapper.h
llliveappconfig.h
lllivefile.h
+ llmainthreadtask.h
llmd5.h
llmemory.h
llmemorystream.h
@@ -230,6 +229,7 @@ set(llcommon_HEADER_FILES
llstaticstringtable.h
llstatsaccumulator.h
llsys.h
+ lltempredirect.h
llthread.h
llthreadlocalstorage.h
llthreadsafequeue.h
@@ -247,6 +247,7 @@ set(llcommon_HEADER_FILES
llwin32headers.h
llwin32headerslean.h
llworkerthread.h
+ lockstatic.h
stdtypes.h
stringize.h
timer.h
@@ -291,7 +292,7 @@ target_link_libraries(
${JSONCPP_LIBRARIES}
${ZLIB_LIBRARIES}
${WINDOWS_LIBRARIES}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_PROGRAM_OPTIONS_LIBRARY}
${BOOST_REGEX_LIBRARY}
@@ -320,13 +321,14 @@ if (LL_TESTS)
${LLCOMMON_LIBRARIES}
${WINDOWS_LIBRARIES}
${GOOGLEMOCK_LIBRARIES}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_THREAD_LIBRARY}
${BOOST_SYSTEM_LIBRARY})
LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llcond "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldeadmantimer "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldependencies "" "${test_libs}")
@@ -338,6 +340,7 @@ if (LL_TESTS)
LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llinstancetracker "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llmainthreadtask "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}")
diff --git a/indra/llcommon/StackWalker.cpp b/indra/llcommon/StackWalker.cpp
index c0d3104099..56defc6465 100644
--- a/indra/llcommon/StackWalker.cpp
+++ b/indra/llcommon/StackWalker.cpp
@@ -98,7 +98,10 @@
// If VC7 and later, then use the shipped 'dbghelp.h'-file
#pragma pack(push,8)
#if _MSC_VER >= 1300
+#pragma warning (push)
+#pragma warning (disable:4091) // a microsoft header has warnings. Very nice.
#include
+#pragma warning (pop)
#else
// inline the important dbghelp.h-declarations...
typedef enum {
@@ -422,7 +425,7 @@ public:
LPSTR m_szSymPath;
#pragma pack(push,8)
-typedef struct IMAGEHLP_MODULE64_V3 {
+struct IMAGEHLP_MODULE64_V3 {
DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64)
DWORD64 BaseOfImage; // base load address of module
DWORD ImageSize; // virtual size of the loaded module
@@ -450,7 +453,7 @@ typedef struct IMAGEHLP_MODULE64_V3 {
BOOL Publics; // contains public symbols
};
-typedef struct IMAGEHLP_MODULE64_V2 {
+struct IMAGEHLP_MODULE64_V2 {
DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64)
DWORD64 BaseOfImage; // base load address of module
DWORD ImageSize; // virtual size of the loaded module
@@ -657,7 +660,7 @@ private:
pGMI = (tGMI) GetProcAddress( hPsapi, "GetModuleInformation" );
if ( (pEPM == NULL) || (pGMFNE == NULL) || (pGMBN == NULL) || (pGMI == NULL) )
{
- // we couldn´t find all functions
+ // we couldn't find all functions
FreeLibrary(hPsapi);
return FALSE;
}
diff --git a/indra/llcommon/StackWalker.h b/indra/llcommon/StackWalker.h
index 834f89c471..4634765d0b 100644
--- a/indra/llcommon/StackWalker.h
+++ b/indra/llcommon/StackWalker.h
@@ -148,7 +148,7 @@ protected:
CHAR loadedImageName[STACKWALK_MAX_NAMELEN];
} CallstackEntry;
- typedef enum CallstackEntryType {firstEntry, nextEntry, lastEntry};
+ enum CallstackEntryType {firstEntry, nextEntry, lastEntry};
virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName);
virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion);
diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp
index 421af3006e..3dab632aef 100644
--- a/indra/llcommon/llapp.cpp
+++ b/indra/llcommon/llapp.cpp
@@ -49,6 +49,8 @@
#include "google_breakpad/exception_handler.h"
#include "stringize.h"
#include "llcleanup.h"
+#include "llevents.h"
+#include "llsdutil.h"
//
// Signal handling
@@ -561,10 +563,42 @@ void LLApp::runErrorHandler()
LLApp::setStopped();
}
+namespace
+{
+
+static std::map statusDesc
+{
+ { LLApp::APP_STATUS_RUNNING, "running" },
+ { LLApp::APP_STATUS_QUITTING, "quitting" },
+ { LLApp::APP_STATUS_STOPPED, "stopped" },
+ { LLApp::APP_STATUS_ERROR, "error" }
+};
+
+} // anonymous namespace
+
// static
void LLApp::setStatus(EAppStatus status)
{
- sStatus = status;
+ sStatus = status;
+
+ // This can also happen very late in the application lifecycle -- don't
+ // resurrect a deleted LLSingleton
+ if (! LLEventPumps::wasDeleted())
+ {
+ // notify interested parties of status change
+ LLSD statsd;
+ auto found = statusDesc.find(status);
+ if (found != statusDesc.end())
+ {
+ statsd = found->second;
+ }
+ else
+ {
+ // unknown status? at least report value
+ statsd = LLSD::Integer(status);
+ }
+ LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", statsd));
+ }
}
diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h
index 0d6637f999..255b50c8d0 100644
--- a/indra/llcommon/llapr.h
+++ b/indra/llcommon/llapr.h
@@ -41,17 +41,7 @@
#include "llstring.h"
-#if LL_WINDOWS
-#pragma warning (push)
-#pragma warning (disable:4265)
-#endif
-// warning C4265: 'std::_Pad' : class has virtual functions, but destructor is not virtual
-
-#include
-
-#if LL_WINDOWS
-#pragma warning (pop)
-#endif
+#include "mutex.h"
struct apr_dso_handle_t;
/**
diff --git a/indra/llcommon/llcond.h b/indra/llcommon/llcond.h
new file mode 100644
index 0000000000..e31b67d893
--- /dev/null
+++ b/indra/llcommon/llcond.h
@@ -0,0 +1,405 @@
+/**
+ * @file llcond.h
+ * @author Nat Goodspeed
+ * @date 2019-07-10
+ * @brief LLCond is a wrapper around condition_variable to encapsulate the
+ * obligatory condition_variable usage pattern. We also provide
+ * simplified versions LLScalarCond, LLBoolCond and LLOneShotCond.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLCOND_H)
+#define LL_LLCOND_H
+
+#include "llunits.h"
+#include "llcoros.h"
+#include LLCOROS_MUTEX_HEADER
+#include "mutex.h"
+#include
+
+/**
+ * LLCond encapsulates the pattern required to use a condition_variable. It
+ * bundles subject data, a mutex and a condition_variable: the three required
+ * data objects. It provides wait() methods analogous to condition_variable,
+ * but using the contained condition_variable and the contained mutex. It
+ * provides modify() methods accepting an invocable to safely modify the
+ * contained data and notify waiters. These methods implicitly perform the
+ * required locking.
+ *
+ * The generic LLCond template assumes that DATA might be a struct or class.
+ * For a scalar DATA type, consider LLScalarCond instead. For specifically
+ * bool, consider LLBoolCond.
+ *
+ * Use of LLCoros::ConditionVariable makes LLCond work between
+ * coroutines as well as between threads.
+ */
+template
+class LLCond
+{
+public:
+ typedef DATA value_type;
+
+private:
+ // This is the DATA controlled by the condition_variable.
+ value_type mData;
+ // condition_variable must be used in conjunction with a mutex. Use
+ // LLCoros::Mutex instead of std::mutex because the latter blocks
+ // the entire calling thread, whereas the former blocks only the current
+ // coroutine within the calling thread. Yet LLCoros::Mutex is safe to
+ // use across threads as well: it subsumes std::mutex functionality.
+ LLCoros::Mutex mMutex;
+ // Use LLCoros::ConditionVariable for the same reason.
+ LLCoros::ConditionVariable mCond;
+
+public:
+ /// LLCond can be explicitly initialized with a specific value for mData if
+ /// desired.
+ LLCond(const value_type& init=value_type()):
+ mData(init)
+ {}
+
+ /// LLCond is move-only
+ LLCond(const LLCond&) = delete;
+ LLCond& operator=(const LLCond&) = delete;
+
+ /// get() returns a const reference to the stored DATA. The only way to
+ /// get a non-const reference -- to modify the stored DATA -- is via
+ /// update_one() or update_all().
+ const value_type& get() const { return mData; }
+
+ /**
+ * Pass update_one() an invocable accepting non-const (DATA&). The
+ * invocable will presumably modify the referenced DATA. update_one()
+ * will lock the mutex, call the invocable and then call notify_one() on
+ * the condition_variable.
+ *
+ * For scalar DATA, it's simpler to use LLScalarCond::set_one(). Use
+ * update_one() when DATA is a struct or class.
+ */
+ template
+ void update_one(MODIFY modify)
+ {
+ { // scope of lock can/should end before notify_one()
+ LLCoros::LockType lk(mMutex);
+ modify(mData);
+ }
+ mCond.notify_one();
+ }
+
+ /**
+ * Pass update_all() an invocable accepting non-const (DATA&). The
+ * invocable will presumably modify the referenced DATA. update_all()
+ * will lock the mutex, call the invocable and then call notify_all() on
+ * the condition_variable.
+ *
+ * For scalar DATA, it's simpler to use LLScalarCond::set_all(). Use
+ * update_all() when DATA is a struct or class.
+ */
+ template
+ void update_all(MODIFY modify)
+ {
+ { // scope of lock can/should end before notify_all()
+ LLCoros::LockType lk(mMutex);
+ modify(mData);
+ }
+ mCond.notify_all();
+ }
+
+ /**
+ * Pass wait() a predicate accepting (const DATA&), returning bool. The
+ * predicate returns true when the condition for which it is waiting has
+ * been satisfied, presumably determined by examining the referenced DATA.
+ * wait() locks the mutex and, until the predicate returns true, calls
+ * wait() on the condition_variable.
+ */
+ template
+ void wait(Pred pred)
+ {
+ LLCoros::LockType lk(mMutex);
+ // We must iterate explicitly since the predicate accepted by
+ // condition_variable::wait() requires a different signature:
+ // condition_variable::wait() calls its predicate with no arguments.
+ // Fortunately, the loop is straightforward.
+ // We advise the caller to pass a predicate accepting (const DATA&).
+ // But what if they instead pass a predicate accepting non-const
+ // (DATA&)? Such a predicate could modify mData, which would be Bad.
+ // Forbid that.
+ while (! pred(const_cast(mData)))
+ {
+ mCond.wait(lk);
+ }
+ }
+
+ /**
+ * Pass wait_for() a chrono::duration, indicating how long we're willing
+ * to wait, and a predicate accepting (const DATA&), returning bool. The
+ * predicate returns true when the condition for which it is waiting has
+ * been satisfied, presumably determined by examining the referenced DATA.
+ * wait_for() locks the mutex and, until the predicate returns true, calls
+ * wait_for() on the condition_variable. wait_for() returns false if
+ * condition_variable::wait_for() timed out without the predicate
+ * returning true.
+ */
+ template
+ bool wait_for(const std::chrono::duration& timeout_duration, Pred pred)
+ {
+ // Instead of replicating wait_until() logic, convert duration to
+ // time_point and just call wait_until().
+ // An implementation in which we repeatedly called
+ // condition_variable::wait_for() with our passed duration would be
+ // wrong! We'd keep pushing the timeout time farther and farther into
+ // the future. This way, we establish a definite timeout time and
+ // stick to it.
+ return wait_until(std::chrono::steady_clock::now() + timeout_duration, pred);
+ }
+
+ /**
+ * This wait_for() overload accepts F32Milliseconds as the duration. Any
+ * duration unit defined in llunits.h is implicitly convertible to
+ * F32Milliseconds. The semantics of this method are the same as the
+ * generic wait_for() method.
+ */
+ template
+ bool wait_for(F32Milliseconds timeout_duration, Pred pred)
+ {
+ return wait_for(convert(timeout_duration), pred);
+ }
+
+protected:
+ // convert F32Milliseconds to a chrono::duration
+ auto convert(F32Milliseconds duration)
+ {
+ // std::chrono::milliseconds doesn't like to be constructed from a
+ // float (F32), rubbing our nose in the thought that
+ // std::chrono::duration::rep is probably integral. Therefore
+ // converting F32Milliseconds to std::chrono::milliseconds would lose
+ // precision. Use std::chrono::microseconds instead. Extract the F32
+ // milliseconds from F32Milliseconds, scale to microseconds, construct
+ // std::chrono::microseconds from that value.
+ return std::chrono::microseconds{ std::chrono::microseconds::rep(duration.value() * 1000) };
+ }
+
+private:
+ /**
+ * Pass wait_until() a chrono::time_point, indicating the time at which we
+ * should stop waiting, and a predicate accepting (const DATA&), returning
+ * bool. The predicate returns true when the condition for which it is
+ * waiting has been satisfied, presumably determined by examining the
+ * referenced DATA. wait_until() locks the mutex and, until the predicate
+ * returns true, calls wait_until() on the condition_variable.
+ * wait_until() returns false if condition_variable::wait_until() timed
+ * out without the predicate returning true.
+ *
+ * Originally this class and its subclasses published wait_until() methods
+ * corresponding to each wait_for() method. But that raised all sorts of
+ * fascinating questions about the time zone of the passed time_point:
+ * local time? server time? UTC? The bottom line is that for LLCond
+ * timeout purposes, we really shouldn't have to care -- timeout duration
+ * is all we need. This private method remains because it's the simplest
+ * way to support iteratively waiting across spurious wakeups while
+ * honoring a fixed timeout.
+ */
+ template
+ bool wait_until(const std::chrono::time_point& timeout_time, Pred pred)
+ {
+ LLCoros::LockType lk(mMutex);
+ // We advise the caller to pass a predicate accepting (const DATA&).
+ // But what if they instead pass a predicate accepting non-const
+ // (DATA&)? Such a predicate could modify mData, which would be Bad.
+ // Forbid that.
+ while (! pred(const_cast(mData)))
+ {
+ if (LLCoros::cv_status::timeout == mCond.wait_until(lk, timeout_time))
+ {
+ // It's possible that wait_until() timed out AND the predicate
+ // became true more or less simultaneously. Even though
+ // wait_until() timed out, check the predicate one more time.
+ return pred(const_cast(mData));
+ }
+ }
+ return true;
+ }
+};
+
+template
+class LLScalarCond: public LLCond
+{
+ using super = LLCond;
+
+public:
+ using typename super::value_type;
+ using super::get;
+ using super::wait;
+ using super::wait_for;
+
+ /// LLScalarCond can be explicitly initialized with a specific value for
+ /// mData if desired.
+ LLScalarCond(const value_type& init=value_type()):
+ super(init)
+ {}
+
+ /// Pass set_one() a new value to which to update mData. set_one() will
+ /// lock the mutex, update mData and then call notify_one() on the
+ /// condition_variable.
+ void set_one(const value_type& value)
+ {
+ super::update_one([&value](value_type& data){ data = value; });
+ }
+
+ /// Pass set_all() a new value to which to update mData. set_all() will
+ /// lock the mutex, update mData and then call notify_all() on the
+ /// condition_variable.
+ void set_all(const value_type& value)
+ {
+ super::update_all([&value](value_type& data){ data = value; });
+ }
+
+ /**
+ * Pass wait_equal() a value for which to wait. wait_equal() locks the
+ * mutex and, until the stored DATA equals that value, calls wait() on the
+ * condition_variable.
+ */
+ void wait_equal(const value_type& value)
+ {
+ super::wait([&value](const value_type& data){ return (data == value); });
+ }
+
+ /**
+ * Pass wait_for_equal() a chrono::duration, indicating how long we're
+ * willing to wait, and a value for which to wait. wait_for_equal() locks
+ * the mutex and, until the stored DATA equals that value, calls
+ * wait_for() on the condition_variable. wait_for_equal() returns false if
+ * condition_variable::wait_for() timed out without the stored DATA being
+ * equal to the passed value.
+ */
+ template
+ bool wait_for_equal(const std::chrono::duration& timeout_duration,
+ const value_type& value)
+ {
+ return super::wait_for(timeout_duration,
+ [&value](const value_type& data){ return (data == value); });
+ }
+
+ /**
+ * This wait_for_equal() overload accepts F32Milliseconds as the duration.
+ * Any duration unit defined in llunits.h is implicitly convertible to
+ * F32Milliseconds. The semantics of this method are the same as the
+ * generic wait_for_equal() method.
+ */
+ bool wait_for_equal(F32Milliseconds timeout_duration, const value_type& value)
+ {
+ return wait_for_equal(super::convert(timeout_duration), value);
+ }
+
+ /**
+ * Pass wait_unequal() a value from which to move away. wait_unequal()
+ * locks the mutex and, until the stored DATA no longer equals that value,
+ * calls wait() on the condition_variable.
+ */
+ void wait_unequal(const value_type& value)
+ {
+ super::wait([&value](const value_type& data){ return (data != value); });
+ }
+
+ /**
+ * Pass wait_for_unequal() a chrono::duration, indicating how long we're
+ * willing to wait, and a value from which to move away.
+ * wait_for_unequal() locks the mutex and, until the stored DATA no longer
+ * equals that value, calls wait_for() on the condition_variable.
+ * wait_for_unequal() returns false if condition_variable::wait_for()
+ * timed out with the stored DATA still being equal to the passed value.
+ */
+ template
+ bool wait_for_unequal(const std::chrono::duration& timeout_duration,
+ const value_type& value)
+ {
+ return super::wait_for(timeout_duration,
+ [&value](const value_type& data){ return (data != value); });
+ }
+
+ /**
+ * This wait_for_unequal() overload accepts F32Milliseconds as the duration.
+ * Any duration unit defined in llunits.h is implicitly convertible to
+ * F32Milliseconds. The semantics of this method are the same as the
+ * generic wait_for_unequal() method.
+ */
+ bool wait_for_unequal(F32Milliseconds timeout_duration, const value_type& value)
+ {
+ return wait_for_unequal(super::convert(timeout_duration), value);
+ }
+
+protected:
+ using super::convert;
+};
+
+/// Using bool as LLScalarCond's DATA seems like a particularly useful case
+using LLBoolCond = LLScalarCond;
+
+/// LLOneShotCond -- init false, set (and wait for) true
+class LLOneShotCond: public LLBoolCond
+{
+ using super = LLBoolCond;
+
+public:
+ using typename super::value_type;
+ using super::get;
+ using super::wait;
+ using super::wait_for;
+ using super::wait_equal;
+ using super::wait_for_equal;
+ using super::wait_unequal;
+ using super::wait_for_unequal;
+
+ /// The bool stored in LLOneShotCond is initially false
+ LLOneShotCond(): super(false) {}
+
+ /// LLOneShotCond assumes that nullary set_one() means to set its bool true
+ void set_one(bool value=true)
+ {
+ super::set_one(value);
+ }
+
+ /// LLOneShotCond assumes that nullary set_all() means to set its bool true
+ void set_all(bool value=true)
+ {
+ super::set_all(value);
+ }
+
+ /**
+ * wait() locks the mutex and, until the stored bool is true, calls wait()
+ * on the condition_variable.
+ */
+ void wait()
+ {
+ super::wait_unequal(false);
+ }
+
+ /**
+ * Pass wait_for() a chrono::duration, indicating how long we're willing
+ * to wait. wait_for() locks the mutex and, until the stored bool is true,
+ * calls wait_for() on the condition_variable. wait_for() returns false if
+ * condition_variable::wait_for() timed out without the stored bool being
+ * true.
+ */
+ template
+ bool wait_for(const std::chrono::duration& timeout_duration)
+ {
+ return super::wait_for_unequal(timeout_duration, false);
+ }
+
+ /**
+ * This wait_for() overload accepts F32Milliseconds as the duration.
+ * Any duration unit defined in llunits.h is implicitly convertible to
+ * F32Milliseconds. The semantics of this method are the same as the
+ * generic wait_for() method.
+ */
+ bool wait_for(F32Milliseconds timeout_duration)
+ {
+ return wait_for(super::convert(timeout_duration));
+ }
+};
+
+#endif /* ! defined(LL_LLCOND_H) */
diff --git a/indra/llcommon/llcoro_get_id.cpp b/indra/llcommon/llcoro_get_id.cpp
deleted file mode 100644
index 24ed1fe0c9..0000000000
--- a/indra/llcommon/llcoro_get_id.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * @file llcoro_get_id.cpp
- * @author Nat Goodspeed
- * @date 2016-09-03
- * @brief Implementation for llcoro_get_id.
- *
- * $LicenseInfo:firstyear=2016&license=viewerlgpl$
- * Copyright (c) 2016, Linden Research, Inc.
- * $/LicenseInfo$
- */
-
-// Precompiled header
-#include "linden_common.h"
-// associated header
-#include "llcoro_get_id.h"
-// STL headers
-// std headers
-// external library headers
-// other Linden headers
-#include "llcoros.h"
-
-namespace llcoro
-{
-
-id get_id()
-{
- // An instance of Current can convert to LLCoros::CoroData*, which can
- // implicitly convert to void*, which is an llcoro::id.
- return LLCoros::Current();
-}
-
-} // llcoro
diff --git a/indra/llcommon/llcoro_get_id.h b/indra/llcommon/llcoro_get_id.h
deleted file mode 100644
index 4c1dca6f19..0000000000
--- a/indra/llcommon/llcoro_get_id.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * @file llcoro_get_id.h
- * @author Nat Goodspeed
- * @date 2016-09-03
- * @brief Supplement the functionality in llcoro.h.
- *
- * This is broken out as a separate header file to resolve
- * circularity: LLCoros isa LLSingleton, yet LLSingleton machinery
- * requires llcoro::get_id().
- *
- * Be very suspicious of anyone else #including this header.
- *
- * $LicenseInfo:firstyear=2016&license=viewerlgpl$
- * Copyright (c) 2016, Linden Research, Inc.
- * $/LicenseInfo$
- */
-
-#if ! defined(LL_LLCORO_GET_ID_H)
-#define LL_LLCORO_GET_ID_H
-
-namespace llcoro
-{
-
-/// Get an opaque, distinct token for the running coroutine (or main).
-typedef void* id;
-id get_id();
-
-} // llcoro
-
-#endif /* ! defined(LL_LLCORO_GET_ID_H) */
diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp
index cc775775bf..262929006d 100644
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -26,15 +26,30 @@
* $/LicenseInfo$
*/
+#include "llwin32headers.h"
+
// Precompiled header
#include "linden_common.h"
// associated header
#include "llcoros.h"
// STL headers
// std headers
+#include
// external library headers
#include
+#include
+#ifndef BOOST_DISABLE_ASSERTS
+#define UNDO_BOOST_DISABLE_ASSERTS
+// with Boost 1.65.1, needed for Mac with this specific header
+#define BOOST_DISABLE_ASSERTS
+#endif
+#include
+#ifdef UNDO_BOOST_DISABLE_ASSERTS
+#undef UNDO_BOOST_DISABLE_ASSERTS
+#undef BOOST_DISABLE_ASSERTS
+#endif
// other Linden headers
+#include "llapp.h"
#include "lltimer.h"
#include "llevents.h"
#include "llerror.h"
@@ -45,85 +60,43 @@
#include
#endif
-namespace {
-void no_op() {}
-} // anonymous namespace
-
-// Do nothing, when we need nothing done. This is a static member of LLCoros
-// because CoroData is a private nested class.
-void LLCoros::no_cleanup(CoroData*) {}
-
-// CoroData for the currently-running coroutine. Use a thread_specific_ptr
-// because each thread potentially has its own distinct pool of coroutines.
-LLCoros::Current::Current()
-{
- // Use a function-static instance so this thread_specific_ptr is
- // instantiated on demand. Since we happen to know it's consumed by
- // LLSingleton, this is likely to happen before the runtime has finished
- // initializing module-static data. For the same reason, we can't package
- // this pointer in an LLSingleton.
-
- // This thread_specific_ptr does NOT own the CoroData object! That's owned
- // by LLCoros::mCoros. It merely identifies it. For this reason we
- // instantiate it with a no-op cleanup function.
- static boost::thread_specific_ptr sCurrent(LLCoros::no_cleanup);
-
- // If this is the first time we're accessing sCurrent for the running
- // thread, its get() will be NULL. This could be a problem, in that
- // llcoro::get_id() would return the same (NULL) token value for the "main
- // coroutine" in every thread, whereas what we really want is a distinct
- // value for every distinct stack in the process. So if get() is NULL,
- // give it a heap CoroData: this ensures that llcoro::get_id() will return
- // distinct values.
- // This tactic is "leaky": sCurrent explicitly does not destroy any
- // CoroData to which it points, and we do NOT enter these "main coroutine"
- // CoroData instances in the LLCoros::mCoros map. They are dummy entries,
- // and they will leak at process shutdown: one CoroData per thread.
- if (! sCurrent.get())
- {
- // It's tempting to provide a distinct name for each thread's "main
- // coroutine." But as getName() has always returned the empty string
- // to mean "not in a coroutine," empty string should suffice here --
- // and truthfully the additional (thread-safe!) machinery to ensure
- // uniqueness just doesn't feel worth the trouble.
- // We use a no-op callable and a minimal stack size because, although
- // CoroData's constructor in fact initializes its mCoro with a
- // coroutine with that stack size, no one ever actually enters it by
- // calling mCoro().
- sCurrent.reset(new CoroData(0, // no prev
- "", // not a named coroutine
- no_op, // no-op callable
- 1024)); // stacksize moot
- }
-
- mCurrent = &sCurrent;
-}
-
-//static
+// static
LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller)
{
- CoroData* current = Current();
- // With the dummy CoroData set in LLCoros::Current::Current(), this
- // pointer should never be NULL.
- llassert_always(current);
+ CoroData* current{ nullptr };
+ // be careful about attempted accesses in the final throes of app shutdown
+ if (! wasDeleted())
+ {
+ current = instance().mCurrent.get();
+ }
+ // For the main() coroutine, the one NOT explicitly launched by launch(),
+ // we never explicitly set mCurrent. Use a static CoroData instance with
+ // canonical values.
+ if (! current)
+ {
+ static std::atomic which_thread(0);
+ // Use alternate CoroData constructor.
+ static thread_local CoroData sMain(which_thread++);
+ // We need not reset() the local_ptr to this instance; we'll simply
+ // find it again every time we discover that current is null.
+ current = &sMain;
+ }
return *current;
}
//static
-LLCoros::coro::self& LLCoros::get_self()
+LLCoros::coro::id LLCoros::get_self()
{
- CoroData& current = get_CoroData("get_self()");
- if (! current.mSelf)
- {
- LL_ERRS("LLCoros") << "Calling get_self() from non-coroutine context!" << LL_ENDL;
- }
- return *current.mSelf;
+ return boost::this_fiber::get_id();
}
//static
void LLCoros::set_consuming(bool consuming)
{
- get_CoroData("set_consuming()").mConsuming = consuming;
+ CoroData& data(get_CoroData("set_consuming()"));
+ // DO NOT call this on the main() coroutine.
+ llassert_always(! data.mName.empty());
+ data.mConsuming = consuming;
}
//static
@@ -132,89 +105,59 @@ bool LLCoros::get_consuming()
return get_CoroData("get_consuming()").mConsuming;
}
-llcoro::Suspending::Suspending()
+// static
+void LLCoros::setStatus(const std::string& status)
{
- LLCoros::Current current;
- // Remember currently-running coroutine: we're about to suspend it.
- mSuspended = current;
- // Revert Current to the value it had at the moment we last switched
- // into this coroutine.
- current.reset(mSuspended->mPrev);
+ get_CoroData("setStatus()").mStatus = status;
}
-llcoro::Suspending::~Suspending()
+// static
+std::string LLCoros::getStatus()
{
- LLCoros::Current current;
- // Okay, we're back, update our mPrev
- mSuspended->mPrev = current;
- // and reinstate our Current.
- current.reset(mSuspended);
+ return get_CoroData("getStatus()").mStatus;
}
LLCoros::LLCoros():
// MAINT-2724: default coroutine stack size too small on Windows.
// Previously we used
// boost::context::guarded_stack_allocator::default_stacksize();
- // empirically this is 64KB on Windows and Linux. Try quadrupling.
+ // empirically this is insufficient.
#if ADDRESS_SIZE == 64
- mStackSize(512*1024)
+ mStackSize(512*1024),
#else
- mStackSize(256*1024)
+ mStackSize(256*1024),
#endif
+ // mCurrent does NOT own the current CoroData instance -- it simply
+ // points to it. So initialize it with a no-op deleter.
+ mCurrent{ [](CoroData*){} }
{
- // Register our cleanup() method for "mainloop" ticks
- LLEventPumps::instance().obtain("mainloop").listen(
- "LLCoros", boost::bind(&LLCoros::cleanup, this, _1));
}
-bool LLCoros::cleanup(const LLSD&)
+LLCoros::~LLCoros()
{
- static std::string previousName;
- static int previousCount = 0;
- // Walk the mCoros map, checking and removing completed coroutines.
- for (CoroMap::iterator mi(mCoros.begin()), mend(mCoros.end()); mi != mend; )
+ printActiveCoroutines("at entry to ~LLCoros()");
+ // Other LLApp status-change listeners do things like close
+ // work queues and inject the Stop exception into pending
+ // promises, to force coroutines waiting on those things to
+ // notice and terminate. The only problem is that by the time
+ // LLApp sets "quitting" status, the main loop has stopped
+ // pumping the fiber scheduler with yield() calls. A waiting
+ // coroutine still might not wake up until after resources on
+ // which it depends have been freed. Pump it a few times
+ // ourselves. Of course, stop pumping as soon as the last of
+ // the coroutines has terminated.
+ for (size_t count = 0; count < 10 && CoroData::instanceCount() > 0; ++count)
{
- // Has this coroutine exited (normal return, exception, exit() call)
- // since last tick?
- if (mi->second->mCoro.exited())
- {
- if (previousName != mi->first)
- {
- previousName = mi->first;
- previousCount = 1;
- }
- else
- {
- ++previousCount;
- }
-
- if ((previousCount < 5) || !(previousCount % 50))
- {
- if (previousCount < 5)
- LL_DEBUGS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL;
- else
- LL_DEBUGS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << "("<< previousCount << ")" << LL_ENDL;
-
- }
- // The erase() call will invalidate its passed iterator value --
- // so increment mi FIRST -- but pass its original value to
- // erase(). This is what postincrement is all about.
- mCoros.erase(mi++);
- }
- else
- {
- // Still live, just skip this entry as if incrementing at the top
- // of the loop as usual.
- ++mi;
- }
+ // don't use llcoro::suspend() because that module depends
+ // on this one
+ boost::this_fiber::yield();
}
- return false;
+ printActiveCoroutines("after pumping");
}
std::string LLCoros::generateDistinctName(const std::string& prefix) const
{
- static std::string previousName;
- static int previousCount = 0;
+ static int unique = 0;
// Allowing empty name would make getName()'s not-found return ambiguous.
if (prefix.empty())
@@ -225,37 +168,15 @@ std::string LLCoros::generateDistinctName(const std::string& prefix) const
// If the specified name isn't already in the map, just use that.
std::string name(prefix);
- // Find the lowest numeric suffix that doesn't collide with an existing
- // entry. Start with 2 just to make it more intuitive for any interested
- // parties: e.g. "joe", "joe2", "joe3"...
- for (int i = 2; ; name = STRINGIZE(prefix << i++))
+ // Until we find an unused name, append a numeric suffix for uniqueness.
+ while (CoroData::getInstance(name))
{
- if (mCoros.find(name) == mCoros.end())
- {
- if (previousName != name)
- {
- previousName = name;
- previousCount = 1;
- }
- else
- {
- ++previousCount;
- }
-
- if ((previousCount < 5) || !(previousCount % 50))
- {
- if (previousCount < 5)
- LL_DEBUGS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL;
- else
- LL_DEBUGS("LLCoros") << "LLCoros: launching coroutine " << name << "(" << previousCount << ")" << LL_ENDL;
-
- }
-
- return name;
- }
+ name = STRINGIZE(prefix << unique++);
}
+ return name;
}
+/*==========================================================================*|
bool LLCoros::kill(const std::string& name)
{
CoroMap::iterator found = mCoros.find(name);
@@ -269,10 +190,19 @@ bool LLCoros::kill(const std::string& name)
mCoros.erase(found);
return true;
}
+|*==========================================================================*/
-std::string LLCoros::getName() const
+//static
+std::string LLCoros::getName()
{
- return Current()->mName;
+ return get_CoroData("getName()").mName;
+}
+
+//static
+std::string LLCoros::logname()
+{
+ LLCoros::CoroData& data(get_CoroData("logname()"));
+ return data.mName.empty()? data.getKey() : data.mName;
}
void LLCoros::setStackSize(S32 stacksize)
@@ -281,25 +211,46 @@ void LLCoros::setStackSize(S32 stacksize)
mStackSize = stacksize;
}
-void LLCoros::printActiveCoroutines()
+void LLCoros::printActiveCoroutines(const std::string& when)
{
- LL_INFOS("LLCoros") << "Number of active coroutines: " << (S32)mCoros.size() << LL_ENDL;
- if (mCoros.size() > 0)
+ LL_INFOS("LLCoros") << "Number of active coroutines " << when
+ << ": " << CoroData::instanceCount() << LL_ENDL;
+ if (CoroData::instanceCount() > 0)
{
LL_INFOS("LLCoros") << "-------------- List of active coroutines ------------";
- CoroMap::iterator iter;
- CoroMap::iterator end = mCoros.end();
F64 time = LLTimer::getTotalSeconds();
- for (iter = mCoros.begin(); iter != end; iter++)
+ for (auto& cd : CoroData::instance_snapshot())
{
- F64 life_time = time - iter->second->mCreationTime;
- LL_CONT << LL_NEWLINE << "Name: " << iter->first << " life: " << life_time;
+ F64 life_time = time - cd.mCreationTime;
+ LL_CONT << LL_NEWLINE
+ << cd.getKey() << ' ' << cd.mStatus << " life: " << life_time;
}
LL_CONT << LL_ENDL;
LL_INFOS("LLCoros") << "-----------------------------------------------------" << LL_ENDL;
}
}
+std::string LLCoros::launch(const std::string& prefix, const callable_t& callable)
+{
+ std::string name(generateDistinctName(prefix));
+ // 'dispatch' means: enter the new fiber immediately, returning here only
+ // when the fiber yields for whatever reason.
+ // std::allocator_arg is a flag to indicate that the following argument is
+ // a StackAllocator.
+ // protected_fixedsize_stack sets a guard page past the end of the new
+ // stack so that stack underflow will result in an access violation
+ // instead of weird, subtle, possibly undiagnosed memory stomps.
+ boost::fibers::fiber newCoro(boost::fibers::launch::dispatch,
+ std::allocator_arg,
+ boost::fibers::protected_fixedsize_stack(mStackSize),
+ [this, &name, &callable](){ toplevel(name, callable); });
+ // You have two choices with a fiber instance: you can join() it or you
+ // can detach() it. If you try to destroy the instance before doing
+ // either, the program silently terminates. We don't need this handle.
+ newCoro.detach();
+ return name;
+}
+
#if LL_WINDOWS
static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific
@@ -337,13 +288,16 @@ void LLCoros::winlevel(const callable_t& callable)
#endif
-// Top-level wrapper around caller's coroutine callable. This function accepts
-// the coroutine library's implicit coro::self& parameter and saves it, but
-// does not pass it down to the caller's callable.
-void LLCoros::toplevel(coro::self& self, CoroData* data, const callable_t& callable)
+// Top-level wrapper around caller's coroutine callable.
+// Normally we like to pass strings and such by const reference -- but in this
+// case, we WANT to copy both the name and the callable to our local stack!
+void LLCoros::toplevel(std::string name, callable_t callable)
{
- // capture the 'self' param in CoroData
- data->mSelf = &self;
+ // keep the CoroData on this top-level function's stack frame
+ CoroData corodata(name);
+ // set it as current
+ mCurrent.reset(&corodata);
+
// run the code the caller actually wants in the coroutine
try
{
@@ -353,75 +307,69 @@ void LLCoros::toplevel(coro::self& self, CoroData* data, const callable_t& calla
callable();
#endif
}
+ catch (const Stop& exc)
+ {
+ LL_INFOS("LLCoros") << "coroutine " << name << " terminating because "
+ << exc.what() << LL_ENDL;
+ }
catch (const LLContinueError&)
{
// Any uncaught exception derived from LLContinueError will be caught
// here and logged. This coroutine will terminate but the rest of the
// viewer will carry on.
- LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << data->mName));
+ LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << name));
}
catch (...)
{
// Any OTHER kind of uncaught exception will cause the viewer to
// crash, hopefully informatively.
- CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << data->mName));
+ CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << name));
}
- // This cleanup isn't perfectly symmetrical with the way we initially set
- // data->mPrev, but this is our last chance to reset Current.
- Current().reset(data->mPrev);
}
-/*****************************************************************************
-* MUST BE LAST
-*****************************************************************************/
-// Turn off MSVC optimizations for just LLCoros::launch() -- see
-// DEV-32777. But MSVC doesn't support push/pop for optimization flags as it
-// does for warning suppression, and we really don't want to force
-// optimization ON for other code even in Debug or RelWithDebInfo builds.
+//static
+void LLCoros::checkStop()
+{
+ if (wasDeleted())
+ {
+ LLTHROW(Shutdown("LLCoros was deleted"));
+ }
+ // do this AFTER the check above, because getName() depends on
+ // get_CoroData(), which depends on the local_ptr in our instance().
+ if (getName().empty())
+ {
+ // Our Stop exception and its subclasses are intended to stop loitering
+ // coroutines. Don't throw it from the main coroutine.
+ return;
+ }
+ if (LLApp::isStopped())
+ {
+ LLTHROW(Stopped("viewer is stopped"));
+ }
+ if (! LLApp::isRunning())
+ {
+ LLTHROW(Stopping("viewer is stopping"));
+ }
+}
-#if LL_MSVC
-// work around broken optimizations
-#pragma warning(disable: 4748)
-#pragma warning(disable: 4355) // 'this' used in initializer list: yes, intentionally
-#pragma optimize("", off)
-#endif // LL_MSVC
-
-LLCoros::CoroData::CoroData(CoroData* prev, const std::string& name,
- const callable_t& callable, S32 stacksize):
- mPrev(prev),
+LLCoros::CoroData::CoroData(const std::string& name):
+ LLInstanceTracker(name),
mName(name),
- // Wrap the caller's callable in our toplevel() function so we can manage
- // Current appropriately at startup and shutdown of each coroutine.
- mCoro(boost::bind(toplevel, _1, this, callable), stacksize),
// don't consume events unless specifically directed
mConsuming(false),
- mSelf(0),
mCreationTime(LLTimer::getTotalSeconds())
{
}
-std::string LLCoros::launch(const std::string& prefix, const callable_t& callable)
+LLCoros::CoroData::CoroData(int n):
+ // This constructor is used for the thread_local instance belonging to the
+ // default coroutine on each thread. We must give each one a different
+ // LLInstanceTracker key because LLInstanceTracker's map spans all
+ // threads, but we want the default coroutine on each thread to have the
+ // empty string as its visible name because some consumers test for that.
+ LLInstanceTracker("main" + stringize(n)),
+ mName(),
+ mConsuming(false),
+ mCreationTime(LLTimer::getTotalSeconds())
{
- std::string name(generateDistinctName(prefix));
- Current current;
- // pass the current value of Current as previous context
- CoroData* newCoro = new(std::nothrow) CoroData(current, name, callable, mStackSize);
- if (newCoro == NULL)
- {
- // Out of memory?
- printActiveCoroutines();
- LL_ERRS("LLCoros") << "Failed to start coroutine: " << name << " Stacksize: " << mStackSize << " Total coroutines: " << mCoros.size() << LL_ENDL;
- }
- // Store it in our pointer map
- mCoros.insert(name, newCoro);
- // also set it as current
- current.reset(newCoro);
- /* Run the coroutine until its first wait, then return here */
- (newCoro->mCoro)(std::nothrow);
- return name;
}
-
-#if LL_MSVC
-// reenable optimizations
-#pragma optimize("", on)
-#endif // LL_MSVC
diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h
index c551413811..38c2356c99 100644
--- a/indra/llcommon/llcoros.h
+++ b/indra/llcommon/llcoros.h
@@ -29,21 +29,26 @@
#if ! defined(LL_LLCOROS_H)
#define LL_LLCOROS_H
-#include
-#include
+#include "llexception.h"
+#include
+#include
+#include
+#include "mutex.h"
#include "llsingleton.h"
-#include
+#include "llinstancetracker.h"
#include
-#include
-#include
#include
-#include
-#include "llcoro_get_id.h" // for friend declaration
-// forward-declare helper class
-namespace llcoro
-{
-class Suspending;
+// e.g. #include LLCOROS_MUTEX_HEADER
+#define LLCOROS_MUTEX_HEADER
+#define LLCOROS_CONDVAR_HEADER
+
+namespace boost {
+ namespace fibers {
+ class mutex;
+ enum class cv_status;
+ class condition_variable;
+ }
}
/**
@@ -76,19 +81,21 @@ class Suspending;
* name prefix; from your prefix it generates a distinct name, registers the
* new coroutine and returns the actual name.
*
- * The name can be used to kill off the coroutine prematurely, if needed. It
- * can also provide diagnostic info: we can look up the name of the
+ * The name
+ * can provide diagnostic info: we can look up the name of the
* currently-running coroutine.
- *
- * Finally, the next frame ("mainloop" event) after the coroutine terminates,
- * LLCoros will notice its demise and destroy it.
*/
class LL_COMMON_API LLCoros: public LLSingleton
{
LLSINGLETON(LLCoros);
+ ~LLCoros();
public:
- /// Canonical boost::dcoroutines::coroutine signature we use
- typedef boost::dcoroutines::coroutine coro;
+ /// The viewer's use of the term "coroutine" became deeply embedded before
+ /// the industry term "fiber" emerged to distinguish userland threads from
+ /// simpler, more transient kinds of coroutines. Semantically they've
+ /// always been fibers. But at this point in history, we're pretty much
+ /// stuck with the term "coroutine."
+ typedef boost::fibers::fiber coro;
/// Canonical callable type
typedef boost::function callable_t;
@@ -119,10 +126,10 @@ public:
* DEV-32777 comments for an explanation.
*
* Pass a nullary callable. It works to directly pass a nullary free
- * function (or static method); for all other cases use boost::bind(). Of
- * course, for a non-static class method, the first parameter must be the
- * class instance. Any other parameters should be passed via the bind()
- * expression.
+ * function (or static method); for other cases use a lambda expression,
+ * std::bind() or boost::bind(). Of course, for a non-static class method,
+ * the first parameter must be the class instance. Any other parameters
+ * should be passed via the enclosing expression.
*
* launch() tweaks the suggested name so it won't collide with any
* existing coroutine instance, creates the coroutine instance, registers
@@ -138,7 +145,7 @@ public:
* one prematurely. Returns @c true if the specified name was found and
* still running at the time.
*/
- bool kill(const std::string& name);
+// bool kill(const std::string& name);
/**
* From within a coroutine, look up the (tweaked) name string by which
@@ -146,16 +153,27 @@ public:
* (e.g. if the coroutine was launched by hand rather than using
* LLCoros::launch()).
*/
- std::string getName() const;
+ static std::string getName();
- /// for delayed initialization
+ /**
+ * This variation returns a name suitable for log messages: the explicit
+ * name for an explicitly-launched coroutine, or "mainN" for the default
+ * coroutine on a thread.
+ */
+ static std::string logname();
+
+ /**
+ * For delayed initialization. To be clear, this will only affect
+ * coroutines launched @em after this point. The underlying facility
+ * provides no way to alter the stack size of any running coroutine.
+ */
void setStackSize(S32 stacksize);
- /// for delayed initialization
- void printActiveCoroutines();
+ /// diagnostic
+ void printActiveCoroutines(const std::string& when=std::string());
- /// get the current coro::self& for those who really really care
- static coro::self& get_self();
+ /// get the current coro::id for those who really really care
+ static coro::id get_self();
/**
* Most coroutines, most of the time, don't "consume" the events for which
@@ -180,6 +198,7 @@ public:
{
set_consuming(consuming);
}
+ OverrideConsuming(const OverrideConsuming&) = delete;
~OverrideConsuming()
{
set_consuming(mPrevConsuming);
@@ -189,142 +208,124 @@ public:
bool mPrevConsuming;
};
+ /// set string coroutine status for diagnostic purposes
+ static void setStatus(const std::string& status);
+ static std::string getStatus();
+
+ /// RAII control of status
+ class TempStatus
+ {
+ public:
+ TempStatus(const std::string& status):
+ mOldStatus(getStatus())
+ {
+ setStatus(status);
+ }
+ TempStatus(const TempStatus&) = delete;
+ ~TempStatus()
+ {
+ setStatus(mOldStatus);
+ }
+
+ private:
+ std::string mOldStatus;
+ };
+
+ /// thrown by checkStop()
+ // It may sound ironic that Stop is derived from LLContinueError, but the
+ // point is that LLContinueError is the category of exception that should
+ // not immediately crash the viewer. Stop and its subclasses are to notify
+ // coroutines that the viewer intends to shut down. The expected response
+ // is to terminate the coroutine, rather than abort the viewer.
+ struct Stop: public LLContinueError
+ {
+ Stop(const std::string& what): LLContinueError(what) {}
+ };
+
+ /// early stages
+ struct Stopping: public Stop
+ {
+ Stopping(const std::string& what): Stop(what) {}
+ };
+
+ /// cleaning up
+ struct Stopped: public Stop
+ {
+ Stopped(const std::string& what): Stop(what) {}
+ };
+
+ /// cleaned up -- not much survives!
+ struct Shutdown: public Stop
+ {
+ Shutdown(const std::string& what): Stop(what) {}
+ };
+
+ /// Call this intermittently if there's a chance your coroutine might
+ /// continue running into application shutdown. Throws Stop if LLCoros has
+ /// been cleaned up.
+ static void checkStop();
+
/**
- * Please do NOT directly use boost::dcoroutines::future! It is essential
- * to maintain the "current" coroutine at every context switch. This
- * Future wraps the essential boost::dcoroutines::future functionality
- * with that maintenance.
+ * Aliases for promise and future. An older underlying future implementation
+ * required us to wrap future; that's no longer needed. However -- if it's
+ * important to restore kill() functionality, we might need to provide a
+ * proxy, so continue using the aliases.
*/
template
- class Future;
+ using Promise = boost::fibers::promise;
+ template
+ using Future = boost::fibers::future;
+ template
+ static Future getFuture(Promise& promise) { return promise.get_future(); }
+
+ // use mutex, lock, condition_variable suitable for coroutines
+ using Mutex = boost::fibers::mutex;
+ using LockType = std::unique_lock;
+ using cv_status = boost::fibers::cv_status;
+ using ConditionVariable = boost::fibers::condition_variable;
+
+ /// for data local to each running coroutine
+ template
+ using local_ptr = boost::fibers::fiber_specific_ptr;
private:
- friend class llcoro::Suspending;
- friend llcoro::id llcoro::get_id();
std::string generateDistinctName(const std::string& prefix) const;
- bool cleanup(const LLSD&);
+ void toplevel(std::string name, callable_t callable);
struct CoroData;
- static void no_cleanup(CoroData*);
#if LL_WINDOWS
static void winlevel(const callable_t& callable);
#endif
- static void toplevel(coro::self& self, CoroData* data, const callable_t& callable);
static CoroData& get_CoroData(const std::string& caller);
S32 mStackSize;
// coroutine-local storage, as it were: one per coro we track
- struct CoroData
+ struct CoroData: public LLInstanceTracker
{
- CoroData(CoroData* prev, const std::string& name,
- const callable_t& callable, S32 stacksize);
+ CoroData(const std::string& name);
+ CoroData(int n);
- // The boost::dcoroutines library supports asymmetric coroutines. Every
- // time we context switch out of a coroutine, we pass control to the
- // previously-active one (or to the non-coroutine stack owned by the
- // thread). So our management of the "current" coroutine must be able to
- // restore the previous value when we're about to switch away.
- CoroData* mPrev;
// tweaked name of the current coroutine
const std::string mName;
- // the actual coroutine instance
- LLCoros::coro mCoro;
// set_consuming() state
bool mConsuming;
- // When the dcoroutine library calls a top-level callable, it implicitly
- // passes coro::self& as the first parameter. All our consumer code used
- // to explicitly pass coro::self& down through all levels of call stack,
- // because at the leaf level we need it for context-switching. But since
- // coroutines are based on cooperative switching, we can cause the
- // top-level entry point to stash a pointer to the currently-running
- // coroutine, and manage it appropriately as we switch out and back in.
- // That eliminates the need to pass it as an explicit parameter down
- // through every level, which is unfortunately viral in nature. Finding it
- // implicitly rather than explicitly allows minor maintenance in which a
- // leaf-level function adds a new async I/O call that suspends the calling
- // coroutine, WITHOUT having to propagate coro::self& through every
- // function signature down to that point -- and of course through every
- // other caller of every such function.
- LLCoros::coro::self* mSelf;
+ // setStatus() state
+ std::string mStatus;
F64 mCreationTime; // since epoch
};
- typedef boost::ptr_map CoroMap;
- CoroMap mCoros;
- // Identify the current coroutine's CoroData. Use a little helper class so
- // a caller can either use a temporary instance, or instantiate a named
- // variable and access it multiple times.
- class Current
- {
- public:
- Current();
-
- operator LLCoros::CoroData*() { return get(); }
- LLCoros::CoroData* operator->() { return get(); }
- LLCoros::CoroData* get() { return mCurrent->get(); }
- void reset(LLCoros::CoroData* ptr) { mCurrent->reset(ptr); }
-
- private:
- boost::thread_specific_ptr* mCurrent;
- };
+ // Identify the current coroutine's CoroData. This local_ptr isn't static
+ // because it's a member of an LLSingleton, and we rely on it being
+ // cleaned up in proper dependency order.
+ local_ptr mCurrent;
};
namespace llcoro
{
-/// Instantiate one of these in a block surrounding any leaf point when
-/// control literally switches away from this coroutine.
-class Suspending: boost::noncopyable
-{
-public:
- Suspending();
- ~Suspending();
+inline
+std::string logname() { return LLCoros::logname(); }
-private:
- LLCoros::CoroData* mSuspended;
-};
-
-} // namespace llcoro
-
-template
-class LLCoros::Future
-{
- typedef boost::dcoroutines::future dfuture;
-
-public:
- Future():
- mFuture(get_self())
- {}
-
- typedef typename boost::dcoroutines::make_callback_result::type callback_t;
-
- callback_t make_callback()
- {
- return boost::dcoroutines::make_callback(mFuture);
- }
-
-#ifndef LL_LINUX
- explicit
-#endif
- operator bool() const
- {
- return bool(mFuture);
- }
-
- bool operator!() const
- {
- return ! mFuture;
- }
-
- T get()
- {
- // instantiate Suspending to manage the "current" coroutine
- llcoro::Suspending suspended;
- return *mFuture;
- }
-
-private:
- dfuture mFuture;
-};
+} // llcoro
#endif /* ! defined(LL_LLCOROS_H) */
diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp
index b46f49ba34..411412c883 100644
--- a/indra/llcommon/llerror.cpp
+++ b/indra/llcommon/llerror.cpp
@@ -39,6 +39,9 @@
#if !LL_WINDOWS
# include
# include
+# include
+#else
+# include
#endif // !LL_WINDOWS
#include
#include "string.h"
@@ -53,6 +56,13 @@
#include "llstl.h"
#include "lltimer.h"
+// On Mac, got:
+// #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define
+// `_GNU_SOURCE` macro or `BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED` if
+// _Unwind_Backtrace is available without `_GNU_SOURCE`."
+#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED
+#include
+
namespace {
#if LL_WINDOWS
void debugger_print(const std::string& s)
@@ -118,27 +128,28 @@ namespace {
class RecordToFile : public LLError::Recorder
{
public:
- RecordToFile(const std::string& filename)
+ RecordToFile(const std::string& filename):
+ mName(filename)
{
mFile.open(filename.c_str(), std::ios_base::out | std::ios_base::app);
if (!mFile)
{
LL_INFOS() << "Error setting log file to " << filename << LL_ENDL;
}
- else
- {
- if (!LLError::getAlwaysFlush())
- {
- mFile.sync_with_stdio(false);
- }
- }
+ else
+ {
+ if (!LLError::getAlwaysFlush())
+ {
+ mFile.sync_with_stdio(false);
+ }
+ }
}
-
+
~RecordToFile()
{
mFile.close();
}
-
+
virtual bool enabled() override
{
#ifdef LL_RELEASE_FOR_DOWNLOAD
@@ -148,11 +159,13 @@ namespace {
#endif
}
- bool okay() { return mFile.good(); }
-
- virtual void recordMessage(LLError::ELevel level,
- const std::string& message) override
- {
+ bool okay() const { return mFile.good(); }
+
+ std::string getFilename() const { return mName; }
+
+ virtual void recordMessage(LLError::ELevel level,
+ const std::string& message) override
+ {
if (LLError::getAlwaysFlush())
{
mFile << message << std::endl;
@@ -161,9 +174,10 @@ namespace {
{
mFile << message << "\n";
}
- }
-
+ }
+
private:
+ const std::string mName;
llofstream mFile;
};
@@ -171,7 +185,7 @@ namespace {
class RecordToStderr : public LLError::Recorder
{
public:
- RecordToStderr(bool timestamp) : mUseANSI(ANSI_PROBE)
+ RecordToStderr(bool timestamp) : mUseANSI(checkANSI())
{
this->showMultiline(true);
}
@@ -193,14 +207,12 @@ namespace {
virtual void recordMessage(LLError::ELevel level,
const std::string& message) override
- {
+ {
static std::string s_ansi_error = createANSI("31"); // red
static std::string s_ansi_warn = createANSI("34"); // blue
static std::string s_ansi_debug = createANSI("35"); // magenta
- mUseANSI = (ANSI_PROBE == mUseANSI) ? (checkANSI() ? ANSI_YES : ANSI_NO) : mUseANSI;
-
- if (ANSI_YES == mUseANSI)
+ if (mUseANSI)
{
writeANSI((level == LLError::LEVEL_ERROR) ? s_ansi_error :
(level == LLError::LEVEL_WARN) ? s_ansi_warn :
@@ -213,12 +225,7 @@ namespace {
}
private:
- enum ANSIState
- {
- ANSI_PROBE,
- ANSI_YES,
- ANSI_NO
- } mUseANSI;
+ bool mUseANSI;
LL_FORCE_INLINE void writeANSI(const std::string& ansi_code, const std::string& message)
{
@@ -229,16 +236,13 @@ namespace {
fprintf(stderr, "%s%s%s\n%s", s_ansi_bold.c_str(), ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() );
}
- bool checkANSI(void)
+ static bool checkANSI(void)
{
-#if LL_LINUX || LL_DARWIN
// Check whether it's okay to use ANSI; if stderr is
// a tty then we assume yes. Can be turned off with
// the LL_NO_ANSI_COLOR env var.
return (0 != isatty(2)) &&
(NULL == getenv("LL_NO_ANSI_COLOR"));
-#endif // LL_LINUX
- return FALSE; // works in a cygwin shell... ;)
}
};
@@ -308,28 +312,35 @@ namespace LLError
{
#ifdef __GNUC__
// GCC: type_info::name() returns a mangled class name,st demangle
- // passing nullptr, 0 forces allocation of a unique buffer we can free
- // fixing MAINT-8724 on OSX 10.14
+ // passing nullptr, 0 forces allocation of a unique buffer we can free
+ // fixing MAINT-8724 on OSX 10.14
int status = -1;
char* name = abi::__cxa_demangle(mangled, nullptr, 0, &status);
- std::string result(name ? name : mangled);
- free(name);
- return result;
+ std::string result(name ? name : mangled);
+ free(name);
+ return result;
+
#elif LL_WINDOWS
- // DevStudio: type_info::name() includes the text "class " at the start
-
- static const std::string class_prefix = "class ";
+ // Visual Studio: type_info::name() includes the text "class " at the start
std::string name = mangled;
- if (0 != name.compare(0, class_prefix.length(), class_prefix))
+ for (const auto& prefix : std::vector{ "class ", "struct " })
{
- LL_DEBUGS() << "Did not see '" << class_prefix << "' prefix on '"
- << name << "'" << LL_ENDL;
- return name;
+ if (0 == name.compare(0, prefix.length(), prefix))
+ {
+ return name.substr(prefix.length());
+ }
}
+ // huh, that's odd, we should see one or the other prefix -- but don't
+ // try to log unless logging is already initialized
+ if (is_available())
+ {
+ // in Python, " or ".join(vector) -- but in C++, a PITB
+ LL_DEBUGS() << "Did not see 'class' or 'struct' prefix on '"
+ << name << "'" << LL_ENDL;
+ }
+ return name;
- return name.substr(class_prefix.length());
-
-#else
+#else // neither GCC nor Visual Studio
return mangled;
#endif
}
@@ -408,7 +419,7 @@ namespace
return false;
}
- if (configuration.isUndefined() || !configuration.isMap() || configuration.emptyMap())
+ if (! configuration || !configuration.isMap())
{
LL_WARNS() << filename() << " missing, ill-formed, or simply undefined"
" content; not changing configuration"
@@ -490,14 +501,11 @@ namespace LLError
LLError::FatalFunction mCrashFunction;
LLError::TimeFunction mTimeFunction;
-
+
Recorders mRecorders;
- RecorderPtr mFileRecorder;
- RecorderPtr mFixedBufferRecorder;
- std::string mFileRecorderFileName;
-
- int mShouldLogCallCounter;
-
+
+ int mShouldLogCallCounter;
+
private:
SettingsConfig();
};
@@ -531,9 +539,6 @@ namespace LLError
mCrashFunction(NULL),
mTimeFunction(NULL),
mRecorders(),
- mFileRecorder(),
- mFixedBufferRecorder(),
- mFileRecorderFileName(),
mShouldLogCallCounter(0)
{
}
@@ -656,22 +661,38 @@ namespace LLError
namespace
{
- bool shouldLogToStderr()
- {
+ bool shouldLogToStderr()
+ {
#if LL_DARWIN
- // On Mac OS X, stderr from apps launched from the Finder goes to the
- // console log. It's generally considered bad form to spam too much
- // there.
-
- // If stdin is a tty, assume the user launched from the command line and
- // therefore wants to see stderr. Otherwise, assume we've been launched
- // from the finder and shouldn't spam stderr.
- return isatty(0);
+ // On Mac OS X, stderr from apps launched from the Finder goes to the
+ // console log. It's generally considered bad form to spam too much
+ // there. That scenario can be detected by noticing that stderr is a
+ // character device (S_IFCHR).
+
+ // If stderr is a tty or a pipe, assume the user launched from the
+ // command line or debugger and therefore wants to see stderr.
+ if (isatty(STDERR_FILENO))
+ return true;
+ // not a tty, but might still be a pipe -- check
+ struct stat st;
+ if (fstat(STDERR_FILENO, &st) < 0)
+ {
+ // capture errno right away, before engaging any other operations
+ auto errno_save = errno;
+ // this gets called during log-system setup -- can't log yet!
+ std::cerr << "shouldLogToStderr: fstat(" << STDERR_FILENO << ") failed, errno "
+ << errno_save << std::endl;
+ // if we can't tell, err on the safe side and don't write stderr
+ return false;
+ }
+
+ // fstat() worked: return true only if stderr is a pipe
+ return ((st.st_mode & S_IFMT) == S_IFIFO);
#else
- return true;
+ return true;
#endif
- }
-
+ }
+
bool stderrLogWantsTime()
{
#if LL_WINDOWS
@@ -685,20 +706,19 @@ namespace
void commonInit(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr = true)
{
LLError::Settings::getInstance()->reset();
-
+
LLError::setDefaultLevel(LLError::LEVEL_INFO);
- LLError::setAlwaysFlush(true);
- LLError::setEnabledLogTypesMask(0xFFFFFFFF);
+ LLError::setAlwaysFlush(true);
+ LLError::setEnabledLogTypesMask(0xFFFFFFFF);
LLError::setFatalFunction(LLError::crashAndLoop);
LLError::setTimeFunction(LLError::utcTime);
// log_to_stderr is only false in the unit and integration tests to keep builds quieter
if (log_to_stderr && shouldLogToStderr())
{
- LLError::RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime()));
- LLError::addRecorder(recordToStdErr);
+ LLError::logToStderr();
}
-
+
#if LL_WINDOWS
LLError::RecorderPtr recordToWinDebug(new RecordToWinDebug());
LLError::addRecorder(recordToWinDebug);
@@ -996,49 +1016,110 @@ namespace LLError
s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder),
s->mRecorders.end());
}
+
+ // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to
+ // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which
+ // points to the Recorder base class), but a shared_ptr which
+ // specifically points to the concrete RECORDER subclass instance, along
+ // with a Recorders::iterator indicating the position of that entry in
+ // mRecorders. The shared_ptr might be empty (operator!() returns true) if
+ // there was no such RECORDER subclass instance in mRecorders.
+ template
+ std::pair, Recorders::iterator>
+ findRecorderPos()
+ {
+ SettingsConfigPtr s = Settings::instance().getSettingsConfig();
+ // Since we promise to return an iterator, use a classic iterator
+ // loop.
+ auto end{s->mRecorders.end()};
+ for (Recorders::iterator it{s->mRecorders.begin()}; it != end; ++it)
+ {
+ // *it is a RecorderPtr, a shared_ptr. Use a
+ // dynamic_pointer_cast to try to downcast to test if it's also a
+ // shared_ptr.
+ auto ptr = boost::dynamic_pointer_cast(*it);
+ if (ptr)
+ {
+ // found the entry we want
+ return { ptr, it };
+ }
+ }
+ // dropped out of the loop without finding any such entry -- instead
+ // of default-constructing Recorders::iterator (which might or might
+ // not be valid), return a value that is valid but not dereferenceable.
+ return { {}, end };
+ }
+
+ // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to
+ // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which
+ // points to the Recorder base class), but a shared_ptr which
+ // specifically points to the concrete RECORDER subclass instance. The
+ // shared_ptr might be empty (operator!() returns true) if there was no
+ // such RECORDER subclass instance in mRecorders.
+ template
+ boost::shared_ptr findRecorder()
+ {
+ return findRecorderPos().first;
+ }
+
+ // Remove an entry from SettingsConfig::mRecorders whose RecorderPtr
+ // points to a Recorder subclass of type RECORDER. Return true if there
+ // was one and we removed it, false if there wasn't one to start with.
+ template
+ bool removeRecorder()
+ {
+ auto found = findRecorderPos();
+ if (found.first)
+ {
+ SettingsConfigPtr s = Settings::instance().getSettingsConfig();
+ s->mRecorders.erase(found.second);
+ }
+ return bool(found.first);
+ }
}
namespace LLError
{
void logToFile(const std::string& file_name)
{
- SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
+ // remove any previous Recorder filling this role
+ removeRecorder();
- removeRecorder(s->mFileRecorder);
- s->mFileRecorder.reset();
- s->mFileRecorderFileName.clear();
-
if (!file_name.empty())
{
- RecorderPtr recordToFile(new RecordToFile(file_name));
- if (boost::dynamic_pointer_cast(recordToFile)->okay())
- {
- s->mFileRecorderFileName = file_name;
- s->mFileRecorder = recordToFile;
- addRecorder(recordToFile);
- }
+ boost::shared_ptr recordToFile(new RecordToFile(file_name));
+ if (recordToFile->okay())
+ {
+ addRecorder(recordToFile);
+ }
}
}
-
- void logToFixedBuffer(LLLineBuffer* fixedBuffer)
- {
- SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
-
- removeRecorder(s->mFixedBufferRecorder);
- s->mFixedBufferRecorder.reset();
-
- if (fixedBuffer)
- {
- RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer));
- s->mFixedBufferRecorder = recordToFixedBuffer;
- addRecorder(recordToFixedBuffer);
- }
- }
std::string logFileName()
{
- SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
- return s->mFileRecorderFileName;
+ auto found = findRecorder();
+ return found? found->getFilename() : std::string();
+ }
+
+ void logToStderr()
+ {
+ if (! findRecorder())
+ {
+ RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime()));
+ addRecorder(recordToStdErr);
+ }
+ }
+
+ void logToFixedBuffer(LLLineBuffer* fixedBuffer)
+ {
+ // remove any previous Recorder filling this role
+ removeRecorder();
+
+ if (fixedBuffer)
+ {
+ RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer));
+ addRecorder(recordToFixedBuffer);
+ }
}
}
@@ -1154,8 +1235,25 @@ namespace
}
namespace {
- LLMutex gLogMutex;
- LLMutex gCallStacksLogMutex;
+ // We need a couple different mutexes, but we want to use the same mechanism
+ // for both. Make getMutex() a template function with different instances
+ // for different MutexDiscriminator values.
+ enum MutexDiscriminator
+ {
+ LOG_MUTEX,
+ STACKS_MUTEX
+ };
+ // Some logging calls happen very early in processing -- so early that our
+ // module-static variables aren't yet initialized. getMutex() wraps a
+ // function-static LLMutex so that early calls can still have a valid
+ // LLMutex instance.
+ template
+ LLMutex* getMutex()
+ {
+ // guaranteed to be initialized the first time control reaches here
+ static LLMutex sMutex;
+ return &sMutex;
+ }
bool checkLevelMap(const LevelMap& map, const std::string& key,
LLError::ELevel& level)
@@ -1203,7 +1301,7 @@ namespace LLError
bool Log::shouldLog(CallSite& site)
{
- LLMutexTrylock lock(&gLogMutex, 5);
+ LLMutexTrylock lock(getMutex(), 5);
if (!lock.isLocked())
{
return false;
@@ -1254,7 +1352,7 @@ namespace LLError
std::ostringstream* Log::out()
{
- LLMutexTrylock lock(&gLogMutex,5);
+ LLMutexTrylock lock(getMutex(),5);
// If we hit a logging request very late during shutdown processing,
// when either of the relevant LLSingletons has already been deleted,
// DO NOT resurrect them.
@@ -1274,7 +1372,7 @@ namespace LLError
void Log::flush(std::ostringstream* out, char* message)
{
- LLMutexTrylock lock(&gLogMutex,5);
+ LLMutexTrylock lock(getMutex(),5);
if (!lock.isLocked())
{
return;
@@ -1314,7 +1412,7 @@ namespace LLError
void Log::flush(std::ostringstream* out, const CallSite& site)
{
- LLMutexTrylock lock(&gLogMutex,5);
+ LLMutexTrylock lock(getMutex(),5);
if (!lock.isLocked())
{
return;
@@ -1486,129 +1584,133 @@ namespace LLError
S32 LLCallStacks::sIndex = 0 ;
//static
- void LLCallStacks::allocateStackBuffer()
- {
- if(sBuffer == NULL)
- {
- sBuffer = new char*[512] ;
- sBuffer[0] = new char[512 * 128] ;
- for(S32 i = 1 ; i < 512 ; i++)
- {
- sBuffer[i] = sBuffer[i-1] + 128 ;
- }
- sIndex = 0 ;
- }
- }
+ void LLCallStacks::allocateStackBuffer()
+ {
+ if(sBuffer == NULL)
+ {
+ sBuffer = new char*[512] ;
+ sBuffer[0] = new char[512 * 128] ;
+ for(S32 i = 1 ; i < 512 ; i++)
+ {
+ sBuffer[i] = sBuffer[i-1] + 128 ;
+ }
+ sIndex = 0 ;
+ }
+ }
- void LLCallStacks::freeStackBuffer()
- {
- if(sBuffer != NULL)
- {
- delete [] sBuffer[0] ;
- delete [] sBuffer ;
- sBuffer = NULL ;
- }
- }
+ void LLCallStacks::freeStackBuffer()
+ {
+ if(sBuffer != NULL)
+ {
+ delete [] sBuffer[0] ;
+ delete [] sBuffer ;
+ sBuffer = NULL ;
+ }
+ }
- //static
- void LLCallStacks::push(const char* function, const int line)
- {
- LLMutexTrylock lock(&gCallStacksLogMutex, 5);
- if (!lock.isLocked())
- {
- return;
- }
+ //static
+ void LLCallStacks::push(const char* function, const int line)
+ {
+ LLMutexTrylock lock(getMutex(), 5);
+ if (!lock.isLocked())
+ {
+ return;
+ }
- if(sBuffer == NULL)
- {
- allocateStackBuffer();
- }
+ if(sBuffer == NULL)
+ {
+ allocateStackBuffer();
+ }
- if(sIndex > 511)
- {
- clear() ;
- }
+ if(sIndex > 511)
+ {
+ clear() ;
+ }
- strcpy(sBuffer[sIndex], function) ;
- sprintf(sBuffer[sIndex] + strlen(function), " line: %d ", line) ;
- sIndex++ ;
+ strcpy(sBuffer[sIndex], function) ;
+ sprintf(sBuffer[sIndex] + strlen(function), " line: %d ", line) ;
+ sIndex++ ;
- return ;
- }
+ return ;
+ }
- //static
- std::ostringstream* LLCallStacks::insert(const char* function, const int line)
- {
- std::ostringstream* _out = LLError::Log::out();
- *_out << function << " line " << line << " " ;
-
- return _out ;
- }
+ //static
+ std::ostringstream* LLCallStacks::insert(const char* function, const int line)
+ {
+ std::ostringstream* _out = LLError::Log::out();
+ *_out << function << " line " << line << " " ;
+ return _out ;
+ }
- //static
- void LLCallStacks::end(std::ostringstream* _out)
- {
- LLMutexTrylock lock(&gCallStacksLogMutex, 5);
- if (!lock.isLocked())
- {
- return;
- }
+ //static
+ void LLCallStacks::end(std::ostringstream* _out)
+ {
+ LLMutexTrylock lock(getMutex(), 5);
+ if (!lock.isLocked())
+ {
+ return;
+ }
- if(sBuffer == NULL)
- {
- allocateStackBuffer();
- }
+ if(sBuffer == NULL)
+ {
+ allocateStackBuffer();
+ }
- if(sIndex > 511)
- {
- clear() ;
- }
+ if(sIndex > 511)
+ {
+ clear() ;
+ }
- LLError::Log::flush(_out, sBuffer[sIndex++]) ;
- }
+ LLError::Log::flush(_out, sBuffer[sIndex++]) ;
+ }
- //static
- void LLCallStacks::print()
- {
- LLMutexTrylock lock(&gCallStacksLogMutex, 5);
- if (!lock.isLocked())
- {
- return;
- }
+ //static
+ void LLCallStacks::print()
+ {
+ LLMutexTrylock lock(getMutex(), 5);
+ if (!lock.isLocked())
+ {
+ return;
+ }
- if(sIndex > 0)
- {
- LL_INFOS() << " ************* PRINT OUT LL CALL STACKS ************* " << LL_ENDL;
- while(sIndex > 0)
- {
- sIndex-- ;
- LL_INFOS() << sBuffer[sIndex] << LL_ENDL;
- }
- LL_INFOS() << " *************** END OF LL CALL STACKS *************** " << LL_ENDL;
- }
+ if(sIndex > 0)
+ {
+ LL_INFOS() << " ************* PRINT OUT LL CALL STACKS ************* " << LL_ENDL;
+ while(sIndex > 0)
+ {
+ sIndex-- ;
+ LL_INFOS() << sBuffer[sIndex] << LL_ENDL;
+ }
+ LL_INFOS() << " *************** END OF LL CALL STACKS *************** " << LL_ENDL;
+ }
- if(sBuffer != NULL)
- {
- freeStackBuffer();
- }
- }
+ if(sBuffer != NULL)
+ {
+ freeStackBuffer();
+ }
+ }
- //static
- void LLCallStacks::clear()
- {
- sIndex = 0 ;
- }
+ //static
+ void LLCallStacks::clear()
+ {
+ sIndex = 0 ;
+ }
- //static
- void LLCallStacks::cleanup()
- {
- freeStackBuffer();
- }
+ //static
+ void LLCallStacks::cleanup()
+ {
+ freeStackBuffer();
+ }
+
+ std::ostream& operator<<(std::ostream& out, const LLStacktrace&)
+ {
+ return out << boost::stacktrace::stacktrace();
+ }
}
bool debugLoggingEnabled(const std::string& tag)
{
- LLMutexTrylock lock(&gLogMutex, 5);
+ LLMutexTrylock lock(getMutex(), 5);
if (!lock.isLocked())
{
return false;
diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h
index 0a78229555..ffaa464d77 100644
--- a/indra/llcommon/llerror.h
+++ b/indra/llcommon/llerror.h
@@ -191,9 +191,9 @@ namespace LLError
The classes CallSite and Log are used by the logging macros below.
They are not intended for general use.
*/
-
+
struct CallSite;
-
+
class LL_COMMON_API Log
{
public:
@@ -202,8 +202,17 @@ namespace LLError
static void flush(std::ostringstream* out, char* message);
static void flush(std::ostringstream*, const CallSite&);
static std::string demangle(const char* mangled);
+ /// classname()
+ template
+ static std::string classname() { return demangle(typeid(T).name()); }
+ /// classname(some_pointer)
+ template
+ static std::string classname(T* const ptr) { return ptr? demangle(typeid(*ptr).name()) : "nullptr"; }
+ /// classname(some_reference)
+ template
+ static std::string classname(const T& obj) { return demangle(typeid(obj).name()); }
};
-
+
struct LL_COMMON_API CallSite
{
// Represents a specific place in the code where a message is logged
@@ -262,30 +271,36 @@ namespace LLError
class LL_COMMON_API NoClassInfo { };
// used to indicate no class info known for logging
- //LLCallStacks keeps track of call stacks and output the call stacks to log file
- //when LLAppViewer::handleViewerCrash() is triggered.
- //
- //Note: to be simple, efficient and necessary to keep track of correct call stacks,
- //LLCallStacks is designed not to be thread-safe.
- //so try not to use it in multiple parallel threads at same time.
- //Used in a single thread at a time is fine.
- class LL_COMMON_API LLCallStacks
- {
- private:
- static char** sBuffer ;
- static S32 sIndex ;
+ //LLCallStacks keeps track of call stacks and output the call stacks to log file
+ //when LLAppViewer::handleViewerCrash() is triggered.
+ //
+ //Note: to be simple, efficient and necessary to keep track of correct call stacks,
+ //LLCallStacks is designed not to be thread-safe.
+ //so try not to use it in multiple parallel threads at same time.
+ //Used in a single thread at a time is fine.
+ class LL_COMMON_API LLCallStacks
+ {
+ private:
+ static char** sBuffer ;
+ static S32 sIndex ;
- static void allocateStackBuffer();
- static void freeStackBuffer();
-
- public:
- static void push(const char* function, const int line) ;
- static std::ostringstream* insert(const char* function, const int line) ;
- static void print() ;
- static void clear() ;
- static void end(std::ostringstream* _out) ;
- static void cleanup();
- };
+ static void allocateStackBuffer();
+ static void freeStackBuffer();
+
+ public:
+ static void push(const char* function, const int line) ;
+ static std::ostringstream* insert(const char* function, const int line) ;
+ static void print() ;
+ static void clear() ;
+ static void end(std::ostringstream* _out) ;
+ static void cleanup();
+ };
+
+ // class which, when streamed, inserts the current stack trace
+ struct LLStacktrace
+ {
+ friend std::ostream& operator<<(std::ostream& out, const LLStacktrace&);
+ };
}
//this is cheaper than llcallstacks if no need to output other variables to call stacks.
@@ -381,8 +396,13 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG;
#define LL_WARNS(...) lllog(LLError::LEVEL_WARN, false, ##__VA_ARGS__)
#define LL_ERRS(...) lllog(LLError::LEVEL_ERROR, false, ##__VA_ARGS__)
// alternative to llassert_always that prints explanatory message
-#define LL_WARNS_IF(exp, ...) if (exp) LL_WARNS(##__VA_ARGS__) << "(" #exp ")"
-#define LL_ERRS_IF(exp, ...) if (exp) LL_ERRS(##__VA_ARGS__) << "(" #exp ")"
+// note ## token paste operator hack used above will only work in gcc following
+// a comma and is completely unnecessary in VS since the comma is automatically
+// suppressed
+// https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
+// https://docs.microsoft.com/en-us/cpp/preprocessor/variadic-macros?view=vs-2015
+#define LL_WARNS_IF(exp, ...) if (exp) LL_WARNS(__VA_ARGS__) << "(" #exp ")"
+#define LL_ERRS_IF(exp, ...) if (exp) LL_ERRS(__VA_ARGS__) << "(" #exp ")"
// Only print the log message once (good for warnings or infos that would otherwise
// spam the log file over and over, such as tighter loops).
diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h
index 276d22fc36..bfa2269025 100644
--- a/indra/llcommon/llerrorcontrol.h
+++ b/indra/llcommon/llerrorcontrol.h
@@ -183,6 +183,7 @@ namespace LLError
// each error message is passed to each recorder via recordMessage()
LL_COMMON_API void logToFile(const std::string& filename);
+ LL_COMMON_API void logToStderr();
LL_COMMON_API void logToFixedBuffer(LLLineBuffer*);
// Utilities to add recorders for logging to a file or a fixed buffer
// A second call to the same function will remove the logger added
diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp
index 56367b8f54..995356dc52 100644
--- a/indra/llcommon/lleventcoro.cpp
+++ b/indra/llcommon/lleventcoro.cpp
@@ -31,17 +31,17 @@
// associated header
#include "lleventcoro.h"
// STL headers
-#include
+#include
+#include
// std headers
// external library headers
+#include
// other Linden headers
#include "llsdserialize.h"
+#include "llsdutil.h"
#include "llerror.h"
#include "llcoros.h"
-#include "llmake.h"
-#include "llexception.h"
-
-#include "lleventfilter.h"
+#include "stringize.h"
namespace
{
@@ -62,7 +62,7 @@ namespace
std::string listenerNameForCoro()
{
// If this coroutine was launched by LLCoros::launch(), find that name.
- std::string name(LLCoros::instance().getName());
+ std::string name(LLCoros::getName());
if (! name.empty())
{
return name;
@@ -92,137 +92,173 @@ std::string listenerNameForCoro()
* In the degenerate case in which @a path is an empty array, @a dest will
* @em become @a value rather than @em containing it.
*/
-void storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& value)
+void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value)
{
- if (rawPath.isUndefined())
+ if (path.isUndefined())
{
// no-op case
return;
}
- // Arrange to treat rawPath uniformly as an array. If it's not already an
- // array, store it as the only entry in one.
- LLSD path;
- if (rawPath.isArray())
- {
- path = rawPath;
- }
- else
- {
- path.append(rawPath);
- }
-
- // Need to indicate a current destination -- but that current destination
- // needs to change as we step through the path array. Where normally we'd
- // use an LLSD& to capture a subscripted LLSD lvalue, this time we must
- // instead use a pointer -- since it must be reassigned.
- LLSD* pdest = &dest;
-
- // Now loop through that array
- for (LLSD::Integer i = 0; i < path.size(); ++i)
- {
- if (path[i].isString())
- {
- // *pdest is an LLSD map
- pdest = &((*pdest)[path[i].asString()]);
- }
- else if (path[i].isInteger())
- {
- // *pdest is an LLSD array
- pdest = &((*pdest)[path[i].asInteger()]);
- }
- else
- {
- // What do we do with Real or Array or Map or ...?
- // As it's a coder error -- not a user error -- rub the coder's
- // face in it so it gets fixed.
- LL_ERRS("lleventcoro") << "storeToLLSDPath(" << dest << ", " << rawPath << ", " << value
- << "): path[" << i << "] bad type " << path[i].type() << LL_ENDL;
- }
- }
-
- // Here *pdest is where we should store value.
- *pdest = value;
+ // Drill down to where we should store 'value'.
+ llsd::drill(dest, path) = value;
}
-/// For LLCoros::Future::make_callback(), the callback has a signature
-/// like void callback(LLSD), which isn't a valid LLEventPump listener: such
-/// listeners must return bool.
-template
-class FutureListener
-{
-public:
- // FutureListener is instantiated on the coroutine stack: the stack, in
- // other words, that wants to suspend.
- FutureListener(const LISTENER& listener):
- mListener(listener),
- // Capture the suspending coroutine's flag as a consuming or
- // non-consuming listener.
- mConsume(LLCoros::get_consuming())
- {}
-
- // operator()() is called on the main stack: the stack on which the
- // expected event is fired.
- bool operator()(const LLSD& event)
- {
- mListener(event);
- // tell upstream LLEventPump whether listener consumed
- return mConsume;
- }
-
-protected:
- LISTENER mListener;
- bool mConsume;
-};
-
} // anonymous
void llcoro::suspend()
{
- // By viewer convention, we post an event on the "mainloop" LLEventPump
- // each iteration of the main event-handling loop. So waiting for a single
- // event on "mainloop" gives us a one-frame suspend.
- suspendUntilEventOn("mainloop");
+ LLCoros::checkStop();
+ LLCoros::TempStatus st("waiting one tick");
+ boost::this_fiber::yield();
}
void llcoro::suspendUntilTimeout(float seconds)
{
- LLEventTimeout timeout;
-
- timeout.eventAfter(seconds, LLSD());
- llcoro::suspendUntilEventOn(timeout);
+ LLCoros::checkStop();
+ // We used to call boost::this_fiber::sleep_for(). But some coroutines
+ // (e.g. LLExperienceCache::idleCoro()) sit in a suspendUntilTimeout()
+ // loop, in which case a sleep_for() call risks sleeping through shutdown.
+ // So instead, listen for "LLApp" state-changing events -- which
+ // fortunately is handled for us by suspendUntilEventOnWithTimeout().
+ // Wait for an event on a bogus LLEventPump on which nobody ever posts
+ // events. Don't make it static because that would force instantiation of
+ // the LLEventPumps LLSingleton registry at static initialization time.
+ // DO allow tweaking the name for uniqueness, this definitely gets
+ // re-entered on multiple coroutines!
+ // We could use an LLUUID if it were important to actively prohibit anyone
+ // from ever posting on this LLEventPump.
+ LLEventStream bogus("xyzzy", true);
+ // Timeout is the NORMAL case for this call!
+ static LLSD timedout;
+ // Deliver, but ignore, timedout when (as usual) we did not receive any
+ // "LLApp" event. The point is that suspendUntilEventOnWithTimeout() will
+ // itself throw Stopping when "LLApp" starts broadcasting shutdown events.
+ suspendUntilEventOnWithTimeout(bogus, seconds, timedout);
}
-LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump,
- const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath)
+namespace
{
- // declare the future
- LLCoros::Future future;
- // make a callback that will assign a value to the future, and listen on
- // the specified LLEventPump with that callback
- std::string listenerName(listenerNameForCoro());
- LLTempBoundListener connection(
- replyPump.getPump().listen(listenerName,
- llmake(future.make_callback())));
+
+// returns a listener on replyPumpP, also on "mainloop" -- both should be
+// stored in LLTempBoundListeners on the caller's stack frame
+std::pair
+postAndSuspendSetup(const std::string& callerName,
+ const std::string& listenerName,
+ LLCoros::Promise& promise,
+ const LLSD& event,
+ const LLEventPumpOrPumpName& requestPumpP,
+ const LLEventPumpOrPumpName& replyPumpP,
+ const LLSD& replyPumpNamePath)
+{
+ // Before we get any farther -- should we be stopping instead of
+ // suspending?
+ LLCoros::checkStop();
+ // Get the consuming attribute for THIS coroutine, the one that's about to
+ // suspend. Don't call get_consuming() in the lambda body: that would
+ // return the consuming attribute for some other coroutine, most likely
+ // the main routine.
+ bool consuming(LLCoros::get_consuming());
+ // listen on the specified LLEventPump with a lambda that will assign a
+ // value to the promise, thus fulfilling its future
+ llassert_always_msg(replyPumpP, ("replyPump required for " + callerName));
+ LLEventPump& replyPump(replyPumpP.getPump());
+ // The relative order of the two listen() calls below would only matter if
+ // "LLApp" were an LLEventMailDrop. But if we ever go there, we'd want to
+ // notice the pending LLApp status first.
+ LLBoundListener stopper(
+ LLEventPumps::instance().obtain("LLApp").listen(
+ listenerName,
+ [&promise, listenerName](const LLSD& status)
+ {
+ // anything except "running" should wake up the waiting
+ // coroutine
+ auto& statsd = status["status"];
+ if (statsd.asString() != "running")
+ {
+ LL_DEBUGS("lleventcoro") << listenerName
+ << " spotted status " << statsd
+ << ", throwing Stopping" << LL_ENDL;
+ try
+ {
+ promise.set_exception(
+ std::make_exception_ptr(
+ LLCoros::Stopping("status " + statsd.asString())));
+ }
+ catch (const boost::fibers::promise_already_satisfied&)
+ {
+ LL_WARNS("lleventcoro") << listenerName
+ << " couldn't throw Stopping "
+ "because promise already set" << LL_ENDL;
+ }
+ }
+ // do not consume -- every listener must see status
+ return false;
+ }));
+ LLBoundListener connection(
+ replyPump.listen(
+ listenerName,
+ [&promise, consuming, listenerName](const LLSD& result)
+ {
+ try
+ {
+ promise.set_value(result);
+ // We did manage to propagate the result value to the
+ // (real) listener. If we're supposed to indicate that
+ // we've consumed it, do so.
+ return consuming;
+ }
+ catch(boost::fibers::promise_already_satisfied & ex)
+ {
+ LL_DEBUGS("lleventcoro") << "promise already satisfied in '"
+ << listenerName << "': " << ex.what() << LL_ENDL;
+ // We could not propagate the result value to the
+ // listener.
+ return false;
+ }
+ }));
+
// skip the "post" part if requestPump is default-constructed
- if (requestPump)
+ if (requestPumpP)
{
+ LLEventPump& requestPump(requestPumpP.getPump());
// If replyPumpNamePath is non-empty, store the replyPump name in the
// request event.
LLSD modevent(event);
- storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName());
- LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName
- << " posting to " << requestPump.getPump().getName()
+ storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getName());
+ LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName
+ << " posting to " << requestPump.getName()
<< LL_ENDL;
// *NOTE:Mani - Removed because modevent could contain user's hashed passwd.
// << ": " << modevent << LL_ENDL;
- requestPump.getPump().post(modevent);
+ requestPump.post(modevent);
}
- LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName
- << " about to wait on LLEventPump " << replyPump.getPump().getName()
+ LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName
+ << " about to wait on LLEventPump " << replyPump.getName()
<< LL_ENDL;
+ return { connection, stopper };
+}
+
+} // anonymous
+
+LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump,
+ const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath)
+{
+ LLCoros::Promise promise;
+ std::string listenerName(listenerNameForCoro());
+
+ // Store both connections into LLTempBoundListeners so we implicitly
+ // disconnect on return from this function.
+ auto connections =
+ postAndSuspendSetup("postAndSuspend()", listenerName, promise,
+ event, requestPump, replyPump, replyPumpNamePath);
+ LLTempBoundListener connection(connections.first), stopper(connections.second);
+
+ // declare the future
+ LLCoros::Future future = LLCoros::getFuture(promise);
// calling get() on the future makes us wait for it
+ LLCoros::TempStatus st(STRINGIZE("waiting for " << replyPump.getPump().getName()));
LLSD value(future.get());
LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName
<< " resuming with " << value << LL_ENDL;
@@ -230,147 +266,52 @@ LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requ
return value;
}
-LLSD llcoro::suspendUntilEventOnWithTimeout(const LLEventPumpOrPumpName& suspendPumpOrName,
- F32 timeoutin, const LLSD &timeoutResult)
+LLSD llcoro::postAndSuspendWithTimeout(const LLSD& event,
+ const LLEventPumpOrPumpName& requestPump,
+ const LLEventPumpOrPumpName& replyPump,
+ const LLSD& replyPumpNamePath,
+ F32 timeout, const LLSD& timeoutResult)
{
- /**
- * The timeout pump is attached upstream of of the waiting pump and will
- * pass the timeout event through it. We CAN NOT attach downstream since
- * doing so will cause the suspendPump to fire any waiting events immediately
- * and they will be lost. This becomes especially problematic with the
- * LLEventTimeout(pump) constructor which will also attempt to fire those
- * events using the virtual listen_impl method in the not yet fully constructed
- * timeoutPump.
- */
- LLEventTimeout timeoutPump;
- LLEventPump &suspendPump = suspendPumpOrName.getPump();
+ LLCoros::Promise promise;
+ std::string listenerName(listenerNameForCoro());
- LLTempBoundListener timeoutListener(timeoutPump.listen(suspendPump.getName(),
- boost::bind(&LLEventPump::post, &suspendPump, _1)));
+ // Store both connections into LLTempBoundListeners so we implicitly
+ // disconnect on return from this function.
+ auto connections =
+ postAndSuspendSetup("postAndSuspendWithTimeout()", listenerName, promise,
+ event, requestPump, replyPump, replyPumpNamePath);
+ LLTempBoundListener connection(connections.first), stopper(connections.second);
- timeoutPump.eventAfter(timeoutin, timeoutResult);
- return llcoro::suspendUntilEventOn(suspendPump);
-}
-
-namespace
-{
-
-/**
- * This helper is specifically for postAndSuspend2(). We use a single future
- * object, but we want to listen on two pumps with it. Since we must still
- * adapt from the callable constructed by boost::dcoroutines::make_callback()
- * (void return) to provide an event listener (bool return), we've adapted
- * FutureListener for the purpose. The basic idea is that we construct a
- * distinct instance of FutureListener2 -- binding different instance data --
- * for each of the pumps. Then, when a pump delivers an LLSD value to either
- * FutureListener2, it can combine that LLSD with its discriminator to feed
- * the future object.
- *
- * DISCRIM is a template argument so we can use llmake() rather than
- * having to write our own argument-deducing helper function.
- */
-template
-class FutureListener2: public FutureListener
-{
- typedef FutureListener super;
-
-public:
- // instantiated on coroutine stack: the stack about to suspend
- FutureListener2(const LISTENER& listener, DISCRIM discriminator):
- super(listener),
- mDiscrim(discriminator)
- {}
-
- // called on main stack: the stack on which event is fired
- bool operator()(const LLSD& event)
- {
- // our future object is defined to accept LLEventWithID
- super::mListener(LLEventWithID(event, mDiscrim));
- // tell LLEventPump whether or not event was consumed
- return super::mConsume;
- }
-
-private:
- const DISCRIM mDiscrim;
-};
-
-} // anonymous
-
-namespace llcoro
-{
-
-LLEventWithID postAndSuspend2(const LLSD& event,
- const LLEventPumpOrPumpName& requestPump,
- const LLEventPumpOrPumpName& replyPump0,
- const LLEventPumpOrPumpName& replyPump1,
- const LLSD& replyPump0NamePath,
- const LLSD& replyPump1NamePath)
-{
// declare the future
- LLCoros::Future future;
- // either callback will assign a value to this future; listen on
- // each specified LLEventPump with a callback
- std::string name(listenerNameForCoro());
- LLTempBoundListener connection0(
- replyPump0.getPump().listen(
- name + "a",
- llmake(future.make_callback(), 0)));
- LLTempBoundListener connection1(
- replyPump1.getPump().listen(
- name + "b",
- llmake(future.make_callback(), 1)));
- // skip the "post" part if requestPump is default-constructed
- if (requestPump)
+ LLCoros::Future future = LLCoros::getFuture(promise);
+ // wait for specified timeout
+ boost::fibers::future_status status;
{
- // If either replyPumpNamePath is non-empty, store the corresponding
- // replyPump name in the request event.
- LLSD modevent(event);
- storeToLLSDPath(modevent, replyPump0NamePath,
- replyPump0.getPump().getName());
- storeToLLSDPath(modevent, replyPump1NamePath,
- replyPump1.getPump().getName());
- LL_DEBUGS("lleventcoro") << "postAndSuspend2(): coroutine " << name
- << " posting to " << requestPump.getPump().getName()
- << ": " << modevent << LL_ENDL;
- requestPump.getPump().post(modevent);
+ LLCoros::TempStatus st(STRINGIZE("waiting for " << replyPump.getPump().getName()
+ << " for " << timeout << "s"));
+ // The fact that we accept non-integer seconds means we should probably
+ // use granularity finer than one second. However, given the overhead of
+ // the rest of our processing, it seems silly to use granularity finer
+ // than a millisecond.
+ status = future.wait_for(std::chrono::milliseconds(long(timeout * 1000)));
}
- LL_DEBUGS("lleventcoro") << "postAndSuspend2(): coroutine " << name
- << " about to wait on LLEventPumps " << replyPump0.getPump().getName()
- << ", " << replyPump1.getPump().getName() << LL_ENDL;
- // calling get() on the future makes us wait for it
- LLEventWithID value(future.get());
- LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << name
- << " resuming with (" << value.first << ", " << value.second << ")"
- << LL_ENDL;
- // returning should disconnect both connections
- return value;
-}
-
-LLSD errorException(const LLEventWithID& result, const std::string& desc)
-{
- // If the result arrived on the error pump (pump 1), instead of
- // returning it, deliver it via exception.
- if (result.second)
+ // if the future is NOT yet ready, return timeoutResult instead
+ if (status == boost::fibers::future_status::timeout)
{
- LLTHROW(LLErrorEvent(desc, result.first));
+ LL_DEBUGS("lleventcoro") << "postAndSuspendWithTimeout(): coroutine " << listenerName
+ << " timed out after " << timeout << " seconds,"
+ << " resuming with " << timeoutResult << LL_ENDL;
+ return timeoutResult;
}
- // That way, our caller knows a simple return must be from the reply
- // pump (pump 0).
- return result.first;
-}
-
-LLSD errorLog(const LLEventWithID& result, const std::string& desc)
-{
- // If the result arrived on the error pump (pump 1), log it as a fatal
- // error.
- if (result.second)
+ else
{
- LL_ERRS("errorLog") << desc << ":" << std::endl;
- LLSDSerialize::toPrettyXML(result.first, LL_CONT);
- LL_CONT << LL_ENDL;
- }
- // A simple return must therefore be from the reply pump (pump 0).
- return result.first;
-}
+ llassert_always(status == boost::fibers::future_status::ready);
-} // namespace llcoro
+ // future is now ready, no more waiting
+ LLSD value(future.get());
+ LL_DEBUGS("lleventcoro") << "postAndSuspendWithTimeout(): coroutine " << listenerName
+ << " resuming with " << value << LL_ENDL;
+ // returning should disconnect the connection
+ return value;
+ }
+}
diff --git a/indra/llcommon/lleventcoro.h b/indra/llcommon/lleventcoro.h
index 84827aab4a..c0fe8b094f 100644
--- a/indra/llcommon/lleventcoro.h
+++ b/indra/llcommon/lleventcoro.h
@@ -29,12 +29,8 @@
#if ! defined(LL_LLEVENTCORO_H)
#define LL_LLEVENTCORO_H
-#include
#include
-#include // std::pair
#include "llevents.h"
-#include "llerror.h"
-#include "llexception.h"
/**
* Like LLListenerOrPumpName, this is a class intended for parameter lists:
@@ -147,117 +143,29 @@ LLSD suspendUntilEventOn(const LLEventPumpOrPumpName& pump)
return postAndSuspend(LLSD(), LLEventPumpOrPumpName(), pump);
}
+/// Like postAndSuspend(), but if we wait longer than @a timeout seconds,
+/// stop waiting and return @a timeoutResult instead.
+LLSD postAndSuspendWithTimeout(const LLSD& event,
+ const LLEventPumpOrPumpName& requestPump,
+ const LLEventPumpOrPumpName& replyPump,
+ const LLSD& replyPumpNamePath,
+ F32 timeout, const LLSD& timeoutResult);
+
/// Suspend the coroutine until an event is fired on the identified pump
/// or the timeout duration has elapsed. If the timeout duration
/// elapses the specified LLSD is returned.
-LLSD suspendUntilEventOnWithTimeout(const LLEventPumpOrPumpName& suspendPumpOrName, F32 timeoutin, const LLSD &timeoutResult);
-
-} // namespace llcoro
-
-/// return type for two-pump variant of suspendUntilEventOn()
-typedef std::pair LLEventWithID;
-
-namespace llcoro
-{
-
-/**
- * This function waits for a reply on either of two specified LLEventPumps.
- * Otherwise, it closely resembles postAndSuspend(); please see the documentation
- * for that function for detailed parameter info.
- *
- * While we could have implemented the single-pump variant in terms of this
- * one, there's enough added complexity here to make it worthwhile to give the
- * single-pump variant its own straightforward implementation. Conversely,
- * though we could use preprocessor logic to generate n-pump overloads up to
- * BOOST_COROUTINE_WAIT_MAX, we don't foresee a use case. This two-pump
- * overload exists because certain event APIs are defined in terms of a reply
- * LLEventPump and an error LLEventPump.
- *
- * The LLEventWithID return value provides not only the received event, but
- * the index of the pump on which it arrived (0 or 1).
- *
- * @note
- * I'd have preferred to overload the name postAndSuspend() for both signatures.
- * But consider the following ambiguous call:
- * @code
- * postAndSuspend(LLSD(), requestPump, replyPump, "someString");
- * @endcode
- * "someString" could be converted to either LLSD (@a replyPumpNamePath for
- * the single-pump function) or LLEventOrPumpName (@a replyPump1 for two-pump
- * function).
- *
- * It seems less burdensome to write postAndSuspend2() than to write either
- * LLSD("someString") or LLEventOrPumpName("someString").
- */
-LLEventWithID postAndSuspend2(const LLSD& event,
- const LLEventPumpOrPumpName& requestPump,
- const LLEventPumpOrPumpName& replyPump0,
- const LLEventPumpOrPumpName& replyPump1,
- const LLSD& replyPump0NamePath=LLSD(),
- const LLSD& replyPump1NamePath=LLSD());
-
-/**
- * Wait for the next event on either of two specified LLEventPumps.
- */
inline
-LLEventWithID
-suspendUntilEventOn(const LLEventPumpOrPumpName& pump0, const LLEventPumpOrPumpName& pump1)
+LLSD suspendUntilEventOnWithTimeout(const LLEventPumpOrPumpName& suspendPumpOrName,
+ F32 timeoutin, const LLSD &timeoutResult)
{
- // This is now a convenience wrapper for postAndSuspend2().
- return postAndSuspend2(LLSD(), LLEventPumpOrPumpName(), pump0, pump1);
+ return postAndSuspendWithTimeout(LLSD(), // event
+ LLEventPumpOrPumpName(), // requestPump
+ suspendPumpOrName, // replyPump
+ LLSD(), // replyPumpNamePath
+ timeoutin,
+ timeoutResult);
}
-/**
- * Helper for the two-pump variant of suspendUntilEventOn(), e.g.:
- *
- * @code
- * LLSD reply = errorException(suspendUntilEventOn(replyPump, errorPump),
- * "error response from login.cgi");
- * @endcode
- *
- * Examines an LLEventWithID, assuming that the second pump (pump 1) is
- * listening for an error indication. If the incoming data arrived on pump 1,
- * throw an LLErrorEvent exception. If the incoming data arrived on pump 0,
- * just return it. Since a normal return can only be from pump 0, we no longer
- * need the LLEventWithID's discriminator int; we can just return the LLSD.
- *
- * @note I'm not worried about introducing the (fairly generic) name
- * errorException() into global namespace, because how many other overloads of
- * the same name are going to accept an LLEventWithID parameter?
- */
-LLSD errorException(const LLEventWithID& result, const std::string& desc);
-
-} // namespace llcoro
-
-/**
- * Exception thrown by errorException(). We don't call this LLEventError
- * because it's not an error in event processing: rather, this exception
- * announces an event that bears error information (for some other API).
- */
-class LL_COMMON_API LLErrorEvent: public LLException
-{
-public:
- LLErrorEvent(const std::string& what, const LLSD& data):
- LLException(what),
- mData(data)
- {}
- virtual ~LLErrorEvent() throw() {}
-
- LLSD getData() const { return mData; }
-
-private:
- LLSD mData;
-};
-
-namespace llcoro
-{
-
-/**
- * Like errorException(), save that this trips a fatal error using LL_ERRS
- * rather than throwing an exception.
- */
-LL_COMMON_API LLSD errorLog(const LLEventWithID& result, const std::string& desc);
-
} // namespace llcoro
/**
@@ -304,84 +212,4 @@ private:
LLEventStream mPump;
};
-/**
- * Other event APIs require the names of two different LLEventPumps: one for
- * success response, the other for error response. Extend LLCoroEventPump
- * for the two-pump use case.
- */
-class LL_COMMON_API LLCoroEventPumps
-{
-public:
- LLCoroEventPumps(const std::string& name="coro",
- const std::string& suff0="Reply",
- const std::string& suff1="Error"):
- mPump0(name + suff0, true), // allow tweaking the pump instance name
- mPump1(name + suff1, true)
- {}
- /// request pump 0's name
- std::string getName0() const { return mPump0.getName(); }
- /// request pump 1's name
- std::string getName1() const { return mPump1.getName(); }
- /// request both names
- std::pair getNames() const
- {
- return std::pair(mPump0.getName(), mPump1.getName());
- }
-
- /// request pump 0
- LLEventPump& getPump0() { return mPump0; }
- /// request pump 1
- LLEventPump& getPump1() { return mPump1; }
-
- /// suspendUntilEventOn(either of our two LLEventPumps)
- LLEventWithID suspend()
- {
- return llcoro::suspendUntilEventOn(mPump0, mPump1);
- }
-
- /// errorException(suspend())
- LLSD suspendWithException()
- {
- return llcoro::errorException(suspend(), std::string("Error event on ") + getName1());
- }
-
- /// errorLog(suspend())
- LLSD suspendWithLog()
- {
- return llcoro::errorLog(suspend(), std::string("Error event on ") + getName1());
- }
-
- LLEventWithID postAndSuspend(const LLSD& event,
- const LLEventPumpOrPumpName& requestPump,
- const LLSD& replyPump0NamePath=LLSD(),
- const LLSD& replyPump1NamePath=LLSD())
- {
- return llcoro::postAndSuspend2(event, requestPump, mPump0, mPump1,
- replyPump0NamePath, replyPump1NamePath);
- }
-
- LLSD postAndSuspendWithException(const LLSD& event,
- const LLEventPumpOrPumpName& requestPump,
- const LLSD& replyPump0NamePath=LLSD(),
- const LLSD& replyPump1NamePath=LLSD())
- {
- return llcoro::errorException(postAndSuspend(event, requestPump,
- replyPump0NamePath, replyPump1NamePath),
- std::string("Error event on ") + getName1());
- }
-
- LLSD postAndSuspendWithLog(const LLSD& event,
- const LLEventPumpOrPumpName& requestPump,
- const LLSD& replyPump0NamePath=LLSD(),
- const LLSD& replyPump1NamePath=LLSD())
- {
- return llcoro::errorLog(postAndSuspend(event, requestPump,
- replyPump0NamePath, replyPump1NamePath),
- std::string("Error event on ") + getName1());
- }
-
-private:
- LLEventStream mPump0, mPump1;
-};
-
#endif /* ! defined(LL_LLEVENTCORO_H) */
diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp
index 9fb18dc67d..4cded7f88e 100644
--- a/indra/llcommon/lleventfilter.cpp
+++ b/indra/llcommon/lleventfilter.cpp
@@ -37,6 +37,9 @@
// other Linden headers
#include "llerror.h" // LL_ERRS
#include "llsdutil.h" // llsd_matches()
+#include "stringize.h"
+#include "lleventtimer.h"
+#include "lldate.h"
/*****************************************************************************
* LLEventFilter
@@ -182,6 +185,27 @@ bool LLEventTimeout::countdownElapsed() const
return mTimer.hasExpired();
}
+LLEventTimer* LLEventTimeout::post_every(F32 period, const std::string& pump, const LLSD& data)
+{
+ return LLEventTimer::run_every(
+ period,
+ [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); });
+}
+
+LLEventTimer* LLEventTimeout::post_at(const LLDate& time, const std::string& pump, const LLSD& data)
+{
+ return LLEventTimer::run_at(
+ time,
+ [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); });
+}
+
+LLEventTimer* LLEventTimeout::post_after(F32 interval, const std::string& pump, const LLSD& data)
+{
+ return LLEventTimer::run_after(
+ interval,
+ [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); });
+}
+
/*****************************************************************************
* LLEventBatch
*****************************************************************************/
@@ -409,3 +433,61 @@ void LLEventBatchThrottle::setSize(std::size_t size)
flush();
}
}
+
+/*****************************************************************************
+* LLEventLogProxy
+*****************************************************************************/
+LLEventLogProxy::LLEventLogProxy(LLEventPump& source, const std::string& name, bool tweak):
+ // note: we are NOT using the constructor that implicitly connects!
+ LLEventFilter(name, tweak),
+ // instead we simply capture a reference to the subject LLEventPump
+ mPump(source)
+{
+}
+
+bool LLEventLogProxy::post(const LLSD& event) /* override */
+{
+ auto counter = mCounter++;
+ auto eventplus = event;
+ if (eventplus.type() == LLSD::TypeMap)
+ {
+ eventplus["_cnt"] = counter;
+ }
+ std::string hdr{STRINGIZE(getName() << ": post " << counter)};
+ LL_INFOS("LogProxy") << hdr << ": " << event << LL_ENDL;
+ bool result = mPump.post(eventplus);
+ LL_INFOS("LogProxy") << hdr << " => " << result << LL_ENDL;
+ return result;
+}
+
+LLBoundListener LLEventLogProxy::listen_impl(const std::string& name,
+ const LLEventListener& target,
+ const NameList& after,
+ const NameList& before)
+{
+ LL_DEBUGS("LogProxy") << "LLEventLogProxy('" << getName() << "').listen('"
+ << name << "')" << LL_ENDL;
+ return mPump.listen(name,
+ [this, name, target](const LLSD& event)->bool
+ { return listener(name, target, event); },
+ after,
+ before);
+}
+
+bool LLEventLogProxy::listener(const std::string& name,
+ const LLEventListener& target,
+ const LLSD& event) const
+{
+ auto eventminus = event;
+ std::string counter{"**"};
+ if (eventminus.has("_cnt"))
+ {
+ counter = stringize(eventminus["_cnt"].asInteger());
+ eventminus.erase("_cnt");
+ }
+ std::string hdr{STRINGIZE(getName() << " to " << name << " " << counter)};
+ LL_INFOS("LogProxy") << hdr << ": " << eventminus << LL_ENDL;
+ bool result = target(eventminus);
+ LL_INFOS("LogProxy") << hdr << " => " << result << LL_ENDL;
+ return result;
+}
diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h
index ff8fc9bc7f..48c2570732 100644
--- a/indra/llcommon/lleventfilter.h
+++ b/indra/llcommon/lleventfilter.h
@@ -32,8 +32,12 @@
#include "llevents.h"
#include "stdtypes.h"
#include "lltimer.h"
+#include "llsdutil.h"
#include
+class LLEventTimer;
+class LLDate;
+
/**
* Generic base class
*/
@@ -210,6 +214,19 @@ public:
LLEventTimeout();
LLEventTimeout(LLEventPump& source);
+ /// using LLEventTimeout as namespace for free functions
+ /// Post event to specified LLEventPump every period seconds. Delete
+ /// returned LLEventTimer* to cancel.
+ static LLEventTimer* post_every(F32 period, const std::string& pump, const LLSD& data);
+ /// Post event to specified LLEventPump at specified future time. Call
+ /// LLEventTimer::getInstance(returned pointer) to check whether it's still
+ /// pending; if so, delete the pointer to cancel.
+ static LLEventTimer* post_at(const LLDate& time, const std::string& pump, const LLSD& data);
+ /// Post event to specified LLEventPump after specified interval. Call
+ /// LLEventTimer::getInstance(returned pointer) to check whether it's still
+ /// pending; if so, delete the pointer to cancel.
+ static LLEventTimer* post_after(F32 interval, const std::string& pump, const LLSD& data);
+
protected:
virtual void setCountdown(F32 seconds);
virtual bool countdownElapsed() const;
@@ -376,4 +393,149 @@ private:
std::size_t mBatchSize;
};
+/**
+ * LLStoreListener self-registers on the LLEventPump of interest, and
+ * unregisters on destruction. As long as it exists, a particular element is
+ * extracted from every event that comes through the upstream LLEventPump and
+ * stored into the target variable.
+ *
+ * This is implemented as a subclass of LLEventFilter, though strictly
+ * speaking it isn't really a "filter" at all: it never passes incoming events
+ * to its own listeners, if any.
+ *
+ * TBD: A variant based on output iterators that stores and then increments
+ * the iterator. Useful with boost::coroutine2!
+ */
+template
+class LLStoreListener: public LLEventFilter
+{
+public:
+ // pass target and optional path to element
+ LLStoreListener(T& target, const LLSD& path=LLSD(), bool consume=false):
+ LLEventFilter("store"),
+ mTarget(target),
+ mPath(path),
+ mConsume(consume)
+ {}
+ // construct and connect
+ LLStoreListener(LLEventPump& source, T& target, const LLSD& path=LLSD(), bool consume=false):
+ LLEventFilter(source, "store"),
+ mTarget(target),
+ mPath(path),
+ mConsume(consume)
+ {}
+
+ // Calling post() with an LLSD event extracts the element indicated by
+ // path, then stores it to mTarget.
+ virtual bool post(const LLSD& event)
+ {
+ // Extract the element specified by 'mPath' from 'event'. To perform a
+ // generic type-appropriate store through mTarget, construct an
+ // LLSDParam and store that, thus engaging LLSDParam's custom
+ // conversions.
+ mTarget = LLSDParam(llsd::drill(event, mPath));
+ return mConsume;
+ }
+
+private:
+ T& mTarget;
+ const LLSD mPath;
+ const bool mConsume;
+};
+
+/*****************************************************************************
+* LLEventLogProxy
+*****************************************************************************/
+/**
+ * LLEventLogProxy is a little different than the other LLEventFilter
+ * subclasses declared in this header file, in that it completely wraps the
+ * passed LLEventPump (both input and output) instead of simply processing its
+ * output. Of course, if someone directly posts to the wrapped LLEventPump by
+ * looking up its string name in LLEventPumps, LLEventLogProxy can't intercept
+ * that post() call. But as long as consuming code is willing to access the
+ * LLEventLogProxy instance instead of the wrapped LLEventPump, all event data
+ * both post()ed and received is logged.
+ *
+ * The proxy role means that LLEventLogProxy intercepts more of LLEventPump's
+ * API than a typical LLEventFilter subclass.
+ */
+class LLEventLogProxy: public LLEventFilter
+{
+ typedef LLEventFilter super;
+public:
+ /**
+ * Construct LLEventLogProxy, wrapping the specified LLEventPump.
+ * Unlike a typical LLEventFilter subclass, the name parameter is @emph
+ * not optional because typically you want LLEventLogProxy to completely
+ * replace the wrapped LLEventPump. So you give the subject LLEventPump
+ * some other name and give the LLEventLogProxy the name that would have
+ * been used for the subject LLEventPump.
+ */
+ LLEventLogProxy(LLEventPump& source, const std::string& name, bool tweak=false);
+
+ /// register a new listener
+ LLBoundListener listen_impl(const std::string& name, const LLEventListener& target,
+ const NameList& after, const NameList& before);
+
+ /// Post an event to all listeners
+ virtual bool post(const LLSD& event) /* override */;
+
+private:
+ /// This method intercepts each call to any target listener. We pass it
+ /// the listener name and the caller's intended target listener plus the
+ /// posted LLSD event.
+ bool listener(const std::string& name,
+ const LLEventListener& target,
+ const LLSD& event) const;
+
+ LLEventPump& mPump;
+ LLSD::Integer mCounter{0};
+};
+
+/**
+ * LLEventPumpHolder is a helper for LLEventLogProxyFor. It simply
+ * stores an instance of T, presumably a subclass of LLEventPump. We derive
+ * LLEventLogProxyFor from LLEventPumpHolder, ensuring that
+ * LLEventPumpHolder's contained mWrappedPump is fully constructed before
+ * passing it to LLEventLogProxyFor's LLEventLogProxy base class constructor.
+ * But since LLEventPumpHolder presents none of the LLEventPump API,
+ * LLEventLogProxyFor inherits its methods unambiguously from
+ * LLEventLogProxy.
+ */
+template
+class LLEventPumpHolder
+{
+protected:
+ LLEventPumpHolder(const std::string& name, bool tweak=false):
+ mWrappedPump(name, tweak)
+ {}
+ T mWrappedPump;
+};
+
+/**
+ * LLEventLogProxyFor is a wrapper around any of the LLEventPump subclasses.
+ * Instantiating an LLEventLogProxy instantiates an internal T. Otherwise
+ * it behaves like LLEventLogProxy.
+ */
+template
+class LLEventLogProxyFor: private LLEventPumpHolder, public LLEventLogProxy
+{
+ // We derive privately from LLEventPumpHolder because it's an
+ // implementation detail of LLEventLogProxyFor. The only reason it's a
+ // base class at all is to guarantee that it's constructed first so we can
+ // pass it to our LLEventLogProxy base class constructor.
+ typedef LLEventPumpHolder holder;
+ typedef LLEventLogProxy super;
+
+public:
+ LLEventLogProxyFor(const std::string& name, bool tweak=false):
+ // our wrapped LLEventPump subclass instance gets a name suffix
+ // because that's not the LLEventPump we want consumers to obtain when
+ // they ask LLEventPumps for this name
+ holder(name + "-", tweak),
+ // it's our LLEventLogProxy that gets the passed name
+ super(holder::mWrappedPump, name, tweak)
+ {}
+};
+
#endif /* ! defined(LL_LLEVENTFILTER_H) */
diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp
index eedd8c92b5..64fb985951 100644
--- a/indra/llcommon/llevents.cpp
+++ b/indra/llcommon/llevents.cpp
@@ -45,6 +45,7 @@
#include
// external library headers
#include
+#include
#if LL_WINDOWS
#pragma warning (push)
#pragma warning (disable : 4701) // compiler thinks might use uninitialized var, but no
@@ -62,52 +63,24 @@
#pragma warning (disable : 4702)
#endif
-/*****************************************************************************
-* queue_names: specify LLEventPump names that should be instantiated as
-* LLEventQueue
-*****************************************************************************/
-/**
- * At present, we recognize particular requested LLEventPump names as needing
- * LLEventQueues. Later on we'll migrate this information to an external
- * configuration file.
- */
-const char* queue_names[] =
-{
- "placeholder - replace with first real name string"
-};
-
-/*****************************************************************************
-* If there's a "mainloop" pump, listen on that to flush all LLEventQueues
-*****************************************************************************/
-struct RegisterFlush : public LLEventTrackable
-{
- RegisterFlush():
- pumps(LLEventPumps::instance())
- {
- pumps.obtain("mainloop").listen("flushLLEventQueues", boost::bind(&RegisterFlush::flush, this, _1));
- }
- bool flush(const LLSD&)
- {
- pumps.flush();
- return false;
- }
- ~RegisterFlush()
- {
- // LLEventTrackable handles stopListening for us.
- }
- LLEventPumps& pumps;
-};
-static RegisterFlush registerFlush;
-
/*****************************************************************************
* LLEventPumps
*****************************************************************************/
LLEventPumps::LLEventPumps():
- // Until we migrate this information to an external config file,
- // initialize mQueueNames from the static queue_names array.
- mQueueNames(boost::begin(queue_names), boost::end(queue_names))
-{
-}
+ mFactories
+ {
+ { "LLEventStream", [](const std::string& name, bool tweak)
+ { return new LLEventStream(name, tweak); } },
+ { "LLEventMailDrop", [](const std::string& name, bool tweak)
+ { 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" }
+ }
+{}
LLEventPump& LLEventPumps::obtain(const std::string& name)
{
@@ -118,14 +91,31 @@ LLEventPump& LLEventPumps::obtain(const std::string& name)
// name.
return *found->second;
}
- // Here we must instantiate an LLEventPump subclass.
- LLEventPump* newInstance;
- // Should this name be an LLEventQueue?
- PumpNames::const_iterator nfound = mQueueNames.find(name);
- if (nfound != mQueueNames.end())
- newInstance = new LLEventQueue(name);
- else
- newInstance = new LLEventStream(name);
+
+ // Here we must instantiate an LLEventPump subclass. Is there a
+ // preregistered class name override for this specific instance name?
+ auto nfound = mTypes.find(name);
+ std::string type;
+ if (nfound != mTypes.end())
+ {
+ type = nfound->second;
+ }
+ // pass tweak=false: we already know there's no existing instance with
+ // this name
+ return make(name, false, type);
+}
+
+LLEventPump& LLEventPumps::make(const std::string& name, bool tweak,
+ const std::string& type)
+{
+ // find the relevant factory for this (or default) type
+ auto found = mFactories.find(type.empty()? "LLEventStream" : type);
+ if (found == mFactories.end())
+ {
+ // Passing an unrecognized type name is a no-no
+ LLTHROW(BadType(type));
+ }
+ auto newInstance = (found->second)(name, tweak);
// LLEventPump's constructor implicitly registers each new instance in
// mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll
// delete it later.
@@ -143,14 +133,23 @@ bool LLEventPumps::post(const std::string&name, const LLSD&message)
return (*found).second->post(message);
}
-
void LLEventPumps::flush()
{
// Flush every known LLEventPump instance. Leave it up to each instance to
// decide what to do with the flush() call.
- for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi)
+ for (PumpMap::value_type& pair : mPumpMap)
{
- pmi->second->flush();
+ pair.second->flush();
+ }
+}
+
+void LLEventPumps::clear()
+{
+ // Clear every known LLEventPump instance. Leave it up to each instance to
+ // decide what to do with the clear() call.
+ for (PumpMap::value_type& pair : mPumpMap)
+ {
+ pair.second->clear();
}
}
@@ -158,9 +157,9 @@ void LLEventPumps::reset()
{
// Reset every known LLEventPump instance. Leave it up to each instance to
// decide what to do with the reset() call.
- for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi)
+ for (PumpMap::value_type& pair : mPumpMap)
{
- pmi->second->reset();
+ pair.second->reset();
}
}
@@ -267,6 +266,9 @@ LLEventPumps::~LLEventPumps()
{
delete *mOurPumps.begin();
}
+ // Reset every remaining registered LLEventPump subclass instance: those
+ // we DIDN'T instantiate using either make() or obtain().
+ reset();
}
/*****************************************************************************
@@ -283,7 +285,7 @@ LLEventPump::LLEventPump(const std::string& name, bool tweak):
// Register every new instance with LLEventPumps
mRegistry(LLEventPumps::instance().getHandle()),
mName(mRegistry.get()->registerNew(*this, name, tweak)),
- mSignal(new LLStandardSignal()),
+ mSignal(boost::make_shared()),
mEnabled(true)
{}
@@ -311,6 +313,14 @@ std::string LLEventPump::inventName(const std::string& pfx)
return STRINGIZE(pfx << suffix++);
}
+void LLEventPump::clear()
+{
+ // Destroy the original LLStandardSignal instance, replacing it with a
+ // whole new one.
+ mSignal = boost::make_shared();
+ mConnections.clear();
+}
+
void LLEventPump::reset()
{
mSignal.reset();
@@ -553,7 +563,7 @@ bool LLEventMailDrop::post(const LLSD& event)
// be posted to any future listeners when they attach.
mEventHistory.push_back(event);
}
-
+
return posted;
}
@@ -583,46 +593,9 @@ LLBoundListener LLEventMailDrop::listen_impl(const std::string& name,
return LLEventStream::listen_impl(name, listener, after, before);
}
-
-/*****************************************************************************
-* LLEventQueue
-*****************************************************************************/
-bool LLEventQueue::post(const LLSD& event)
+void LLEventMailDrop::discard()
{
- if (mEnabled)
- {
- // Defer sending this event by queueing it until flush()
- mEventQueue.push_back(event);
- }
- // Unconditionally return false. We won't know until flush() whether a
- // listener claims to have handled the event -- meanwhile, don't block
- // other listeners.
- return false;
-}
-
-void LLEventQueue::flush()
-{
- if(!mSignal) return;
-
- // Consider the case when a given listener on this LLEventQueue posts yet
- // another event on the same queue. If we loop over mEventQueue directly,
- // we'll end up processing all those events during the same flush() call
- // -- rather like an EventStream. Instead, copy mEventQueue and clear it,
- // so that any new events posted to this LLEventQueue during flush() will
- // be processed in the *next* flush() call.
- EventQueue queue(mEventQueue);
- mEventQueue.clear();
- // NOTE NOTE NOTE: Any new access to member data beyond this point should
- // cause us to move our LLStandardSignal object to a pimpl class along
- // with said member data. Then the local shared_ptr will preserve both.
-
- // DEV-43463: capture a local copy of mSignal. See LLEventStream::post()
- // for detailed comments.
- boost::shared_ptr signal(mSignal);
- for ( ; ! queue.empty(); queue.pop_front())
- {
- (*signal)(queue.front());
- }
+ mEventHistory.clear();
}
/*****************************************************************************
diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h
index 62d97007ac..e380c108f4 100644
--- a/indra/llcommon/llevents.h
+++ b/indra/llcommon/llevents.h
@@ -37,6 +37,7 @@
#include
#include
#include
+#include
#if LL_WINDOWS
#pragma warning (push)
#pragma warning (disable : 4263) // boost::signals2::expired_slot::what() has const mismatch
@@ -55,7 +56,6 @@
#include
#include // reference_wrapper
#include
-#include
#include
#include "llsd.h"
#include "llsingleton.h"
@@ -211,8 +211,7 @@ public:
/// exception if you try to call when empty
struct Empty: public LLException
{
- Empty(const std::string& what):
- LLException(std::string("LLListenerOrPumpName::Empty: ") + what) {}
+ Empty(const std::string& what): LLException("LLListenerOrPumpName::Empty: " + what) {}
};
private:
@@ -247,6 +246,30 @@ public:
*/
LLEventPump& obtain(const std::string& name);
+ /// exception potentially thrown by make()
+ struct BadType: public LLException
+ {
+ BadType(const std::string& what): LLException("BadType: " + what) {}
+ };
+
+ /**
+ * Create an LLEventPump with suggested name (optionally of specified
+ * LLEventPump subclass type). As with obtain(), LLEventPumps owns the new
+ * instance.
+ *
+ * As with LLEventPump's constructor, make() could throw
+ * LLEventPump::DupPumpName unless you pass tweak=true.
+ *
+ * As with a hand-constructed LLEventPump subclass, if you pass
+ * tweak=true, the tweaked name can be obtained by LLEventPump::getName().
+ *
+ * Pass empty type to get the default LLEventStream.
+ *
+ * If you pass an unrecognized type string, make() throws BadType.
+ */
+ LLEventPump& make(const std::string& name, bool tweak=false,
+ const std::string& type=std::string());
+
/**
* Find the named LLEventPump instance. If it exists post the message to it.
* If the pump does not exist, do nothing.
@@ -263,6 +286,11 @@ public:
*/
void flush();
+ /**
+ * Disconnect listeners from all known LLEventPump instances
+ */
+ void clear();
+
/**
* Reset all known LLEventPump instances
* workaround for DEV-35406 crash on shutdown
@@ -298,43 +326,21 @@ testable:
// destroyed.
typedef std::set PumpSet;
PumpSet mOurPumps;
- // LLEventPump names that should be instantiated as LLEventQueue rather
- // than as LLEventStream
- typedef std::set PumpNames;
- PumpNames mQueueNames;
+ // for make(), map string type name to LLEventPump subclass factory function
+ typedef std::map> PumpFactories;
+ // 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;
+
+ // for obtain(), map desired string instance name to string type when
+ // obtain() must create the instance
+ typedef std::map InstanceTypes;
+ InstanceTypes mTypes;
};
-/*****************************************************************************
-* details
-*****************************************************************************/
-namespace LLEventDetail
-{
- /// Any callable capable of connecting an LLEventListener to an
- /// LLStandardSignal to produce an LLBoundListener can be mapped to this
- /// signature.
- typedef boost::function ConnectFunc;
-
- /// overload of visit_and_connect() when we have a string identifier available
- template
- LLBoundListener visit_and_connect(const std::string& name,
- const LISTENER& listener,
- const ConnectFunc& connect_func);
- /**
- * Utility template function to use Visitor appropriately
- *
- * @param listener Callable to connect, typically a boost::bind()
- * expression. This will be visited by Visitor using boost::visit_each().
- * @param connect_func Callable that will connect() @a listener to an
- * LLStandardSignal, returning LLBoundListener.
- */
- template
- LLBoundListener visit_and_connect(const LISTENER& listener,
- const ConnectFunc& connect_func)
- {
- return visit_and_connect("", listener, connect_func);
- }
-} // namespace LLEventDetail
-
/*****************************************************************************
* LLEventTrackable
*****************************************************************************/
@@ -369,11 +375,6 @@ namespace LLEventDetail
* instance, it attempts to dereference the Foo* pointer that was
* deleted but not zeroed.)
* - Undefined behavior results.
- * If you suspect you may encounter any such scenario, you're better off
- * managing the lifespan of your object with boost::shared_ptr.
- * Passing LLEventPump::listen() a boost::bind() expression
- * involving a boost::weak_ptr is recognized specially, engaging
- * thread-safe Boost.Signals2 machinery.
*/
typedef boost::signals2::trackable LLEventTrackable;
@@ -382,7 +383,7 @@ typedef boost::signals2::trackable LLEventTrackable;
*****************************************************************************/
/**
* LLEventPump is the base class interface through which we access the
- * concrete subclasses LLEventStream and LLEventQueue.
+ * concrete subclasses such as LLEventStream.
*
* @NOTE
* LLEventPump derives from LLEventTrackable so that when you "chain"
@@ -403,8 +404,7 @@ public:
*/
struct DupPumpName: public LLException
{
- DupPumpName(const std::string& what):
- LLException(std::string("DupPumpName: ") + what) {}
+ DupPumpName(const std::string& what): LLException("DupPumpName: " + what) {}
};
/**
@@ -440,9 +440,7 @@ public:
*/
struct DupListenerName: public ListenError
{
- DupListenerName(const std::string& what):
- ListenError(std::string("DupListenerName: ") + what)
- {}
+ DupListenerName(const std::string& what): ListenError("DupListenerName: " + what) {}
};
/**
* exception thrown by listen(). The order dependencies specified for your
@@ -454,7 +452,7 @@ public:
*/
struct Cycle: public ListenError
{
- Cycle(const std::string& what): ListenError(std::string("Cycle: ") + what) {}
+ Cycle(const std::string& what): ListenError("Cycle: " + what) {}
};
/**
* exception thrown by listen(). This one means that your new listener
@@ -475,7 +473,7 @@ public:
*/
struct OrderChange: public ListenError
{
- OrderChange(const std::string& what): ListenError(std::string("OrderChange: ") + what) {}
+ OrderChange(const std::string& what): ListenError("OrderChange: " + what) {}
};
/// used by listen()
@@ -512,44 +510,13 @@ public:
* the result be assigned to a LLTempBoundListener or the listener is
* manually disconnected when no longer needed since there will be no
* way to later find and disconnect this listener manually.
- *
- * If (as is typical) you pass a boost::bind() expression as @a
- * listener, listen() will inspect the components of that expression. If a
- * bound object matches any of several cases, the connection will
- * automatically be disconnected when that object is destroyed.
- *
- * * You bind a boost::weak_ptr.
- * * Binding a boost::shared_ptr that way would ensure that the
- * referenced object would @em never be destroyed, since the @c
- * shared_ptr stored in the LLEventPump would remain an outstanding
- * reference. Use the weaken() function to convert your @c shared_ptr to
- * @c weak_ptr. Because this is easy to forget, binding a @c shared_ptr
- * will produce a compile error (@c BOOST_STATIC_ASSERT failure).
- * * You bind a simple pointer or reference to an object derived from
- * boost::enable_shared_from_this. (UNDER CONSTRUCTION)
- * * You bind a simple pointer or reference to an object derived from
- * LLEventTrackable. Unlike the cases described above, though, this is
- * vulnerable to a couple of cross-thread race conditions, as described
- * in the LLEventTrackable documentation.
*/
- template
- LLBoundListener listen(const std::string& name, const LISTENER& listener,
+ LLBoundListener listen(const std::string& name,
+ const LLEventListener& listener,
const NameList& after=NameList(),
const NameList& before=NameList())
{
- // Examine listener, using our listen_impl() method to make the
- // actual connection.
- // This is why listen() is a template. Conversion from boost::bind()
- // to LLEventListener performs type erasure, so it's important to look
- // at the boost::bind object itself before that happens.
- return LLEventDetail::visit_and_connect(name,
- listener,
- boost::bind(&LLEventPump::listen_invoke,
- this,
- name,
- _1,
- after,
- before));
+ return listen_impl(name, listener, after, before);
}
/// Get the LLBoundListener associated with the passed name (dummy
@@ -587,19 +554,12 @@ public:
private:
friend class LLEventPumps;
-
+ virtual void clear();
virtual void reset();
private:
- LLBoundListener listen_invoke(const std::string& name, const LLEventListener& listener,
- const NameList& after,
- const NameList& before)
- {
- return this->listen_impl(name, listener, after, before);
- }
-
// must precede mName; see LLEventPump::LLEventPump()
LLHandle mRegistry;
@@ -663,11 +623,10 @@ public:
* event *must* eventually reach a listener that will consume it, else the
* queue will grow to arbitrary length.
*
- * @NOTE: When using an LLEventMailDrop (or LLEventQueue) with a LLEventTimeout or
+ * @NOTE: When using an LLEventMailDrop with an LLEventTimeout or
* LLEventFilter attaching the filter downstream, using Timeout's constructor will
* cause the MailDrop to discharge any of its stored events. The timeout should
* instead be connected upstream using its listen() method.
- * See llcoro::suspendUntilEventOnWithTimeout() for an example.
*/
class LL_COMMON_API LLEventMailDrop : public LLEventStream
{
@@ -679,7 +638,8 @@ public:
virtual bool post(const LLSD& event) override;
/// Remove any history stored in the mail drop.
- virtual void flush() override { mEventHistory.clear(); LLEventStream::flush(); };
+ void discard();
+
protected:
virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&,
const NameList& after,
@@ -690,30 +650,6 @@ private:
EventList mEventHistory;
};
-/*****************************************************************************
-* LLEventQueue
-*****************************************************************************/
-/**
- * LLEventQueue is a LLEventPump whose post() method defers calling registered
- * listeners until flush() is called.
- */
-class LL_COMMON_API LLEventQueue: public LLEventPump
-{
-public:
- LLEventQueue(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {}
- virtual ~LLEventQueue() {}
-
- /// Post an event to all listeners
- virtual bool post(const LLSD& event);
-
- /// flush queued events
- virtual void flush();
-
-private:
- typedef std::deque EventQueue;
- EventQueue mEventQueue;
-};
-
/*****************************************************************************
* LLReqID
*****************************************************************************/
@@ -809,329 +745,6 @@ private:
LL_COMMON_API bool sendReply(const LLSD& reply, const LLSD& request,
const std::string& replyKey="reply");
-/**
- * Base class for LLListenerWrapper. See visit_and_connect() and llwrap(). We
- * provide virtual @c accept_xxx() methods, customization points allowing a
- * subclass access to certain data visible at LLEventPump::listen() time.
- * Example subclass usage:
- *
- * @code
- * myEventPump.listen("somename",
- * llwrap(boost::bind(&MyClass::method, instance, _1)));
- * @endcode
- *
- * Because of the anticipated usage (note the anonymous temporary
- * MyListenerWrapper instance in the example above), the @c accept_xxx()
- * methods must be @c const.
- */
-class LL_COMMON_API LLListenerWrapperBase
-{
-public:
- /// New instance. The accept_xxx() machinery makes it important to use
- /// shared_ptrs for our data. Many copies of this object are made before
- /// the instance that actually ends up in the signal, yet accept_xxx()
- /// will later be called on the @em original instance. All copies of the
- /// same original instance must share the same data.
- LLListenerWrapperBase():
- mName(new std::string),
- mConnection(new LLBoundListener)
- {
- }
-
- /// Copy constructor. Copy shared_ptrs to original instance data.
- LLListenerWrapperBase(const LLListenerWrapperBase& that):
- mName(that.mName),
- mConnection(that.mConnection)
- {
- }
- virtual ~LLListenerWrapperBase() {}
-
- /// Ask LLEventPump::listen() for the listener name
- virtual void accept_name(const std::string& name) const
- {
- *mName = name;
- }
-
- /// Ask LLEventPump::listen() for the new connection
- virtual void accept_connection(const LLBoundListener& connection) const
- {
- *mConnection = connection;
- }
-
-protected:
- /// Listener name.
- boost::shared_ptr mName;
- /// Connection.
- boost::shared_ptr mConnection;
-};
-
-/*****************************************************************************
-* Underpinnings
-*****************************************************************************/
-/**
- * We originally provided a suite of overloaded
- * LLEventTrackable::listenTo(LLEventPump&, ...) methods that would call
- * LLEventPump::listen(...) and then pass the returned LLBoundListener to
- * LLEventTrackable::track(). This was workable but error-prone: the coder
- * must remember to call listenTo() rather than the more straightforward
- * listen() method.
- *
- * Now we publish only the single canonical listen() method, so there's a
- * uniform mechanism. Having a single way to do this is good, in that there's
- * no question in the coder's mind which of several alternatives to choose.
- *
- * To support automatic connection management, we use boost::visit_each
- * (http://www.boost.org/doc/libs/1_37_0/doc/html/boost/visit_each.html) to
- * inspect each argument of a boost::bind expression. (Although the visit_each
- * mechanism was first introduced with the original Boost.Signals library, it
- * was only later documented.)
- *
- * Cases:
- * * At least one of the function's arguments is a boost::weak_ptr. Pass
- * the corresponding shared_ptr to slot_type::track(). Ideally that would be
- * the object whose method we want to call, but in fact we do the same for
- * any weak_ptr we might find among the bound arguments. If we're passing
- * our bound method a weak_ptr to some object, wouldn't the destruction of
- * that object invalidate the call? So we disconnect automatically when any
- * such object is destroyed. This is the mechanism preferred by boost::
- * signals2.
- * * One of the functions's arguments is a boost::shared_ptr. This produces
- * a compile error: the bound copy of the shared_ptr stored in the
- * boost_bind object stored in the signal object would make the referenced
- * T object immortal. We provide a weaken() function. Pass
- * weaken(your_shared_ptr) instead. (We can inspect, but not modify, the
- * boost::bind object. Otherwise we'd replace the shared_ptr with weak_ptr
- * implicitly and just proceed.)
- * * One of the function's arguments is a plain pointer/reference to an object
- * derived from boost::enable_shared_from_this. We assume that this object
- * is managed using boost::shared_ptr, so we implicitly extract a shared_ptr
- * and track that. (UNDER CONSTRUCTION)
- * * One of the function's arguments is derived from LLEventTrackable. Pass
- * the LLBoundListener to its LLEventTrackable::track(). This is vulnerable
- * to a couple different race conditions, as described in LLEventTrackable
- * documentation. (NOTE: Now that LLEventTrackable is a typedef for
- * boost::signals2::trackable, the Signals2 library handles this itself, so
- * our visitor needs no special logic for this case.)
- * * Any other argument type is irrelevant to automatic connection management.
- */
-
-namespace LLEventDetail
-{
- template
- const F& unwrap(const F& f) { return f; }
-
- template
- const F& unwrap(const boost::reference_wrapper& f) { return f.get(); }
-
- // Most of the following is lifted from the Boost.Signals use of
- // visit_each.
- template struct truth {};
-
- /**
- * boost::visit_each() Visitor, used on a template argument const F&
- * f as follows (see visit_and_connect()):
- * @code
- * LLEventListener listener(f);
- * Visitor visitor(listener); // bind listener so it can track() shared_ptrs
- * using boost::visit_each; // allow unqualified visit_each() call for ADL
- * visit_each(visitor, unwrap(f));
- * @endcode
- */
- class Visitor
- {
- public:
- /**
- * Visitor binds a reference to LLEventListener so we can track() any
- * shared_ptrs we find in the argument list.
- */
- Visitor(LLEventListener& listener):
- mListener(listener)
- {
- }
-
- /**
- * boost::visit_each() calls this method for each component of a
- * boost::bind() expression.
- */
- template
- void operator()(const T& t) const
- {
- decode(t, 0);
- }
-
- private:
- // decode() decides between a reference wrapper and anything else
- // boost::ref() variant
- template
- void decode(const boost::reference_wrapper& t, int) const
- {
-// add_if_trackable(t.get_pointer());
- }
-
- // decode() anything else
- template
- void decode(const T& t, long) const
- {
- typedef truth<(boost::is_pointer::value)> is_a_pointer;
- maybe_get_pointer(t, is_a_pointer());
- }
-
- // maybe_get_pointer() decides between a pointer and a non-pointer
- // plain pointer variant
- template
- void maybe_get_pointer(const T& t, truth) const
- {
-// add_if_trackable(t);
- }
-
- // shared_ptr variant
- template
- void maybe_get_pointer(const boost::shared_ptr& t, truth) const
- {
- // If we have a shared_ptr to this object, it doesn't matter
- // whether the object is derived from LLEventTrackable, so no
- // further analysis of T is needed.
-// mListener.track(t);
-
- // Make this case illegal. Passing a bound shared_ptr to
- // slot_type::track() is useless, since the bound shared_ptr will
- // keep the object alive anyway! Force the coder to cast to weak_ptr.
-
- // Trivial as it is, make the BOOST_STATIC_ASSERT() condition
- // dependent on template param so the macro is only evaluated if
- // this method is in fact instantiated, as described here:
- // http://www.boost.org/doc/libs/1_34_1/doc/html/boost_staticassert.html
-
- // ATTENTION: Don't bind a shared_ptr using
- // LLEventPump::listen(boost::bind()). Doing so captures a copy of
- // the shared_ptr, making the referenced object effectively
- // immortal. Use the weaken() function, e.g.:
- // somepump.listen(boost::bind(...weaken(my_shared_ptr)...));
- // This lets us automatically disconnect when the referenced
- // object is destroyed.
- BOOST_STATIC_ASSERT(sizeof(T) == 0);
- }
-
- // weak_ptr variant
- template
- void maybe_get_pointer(const boost::weak_ptr& t, truth) const
- {
- // If we have a weak_ptr to this object, it doesn't matter
- // whether the object is derived from LLEventTrackable, so no
- // further analysis of T is needed.
- mListener.track(t);
-// std::cout << "Found weak_ptr<" << typeid(T).name() << ">!\n";
- }
-
-#if 0
- // reference to anything derived from boost::enable_shared_from_this
- template
- inline void maybe_get_pointer(const boost::enable_shared_from_this& ct,
- truth) const
- {
- // Use the slot_type::track(shared_ptr) mechanism. Cast away
- // const-ness because (in our code base anyway) it's unusual
- // to find shared_ptr.
- boost::enable_shared_from_this&
- t(const_cast&>(ct));
- std::cout << "Capturing shared_from_this()" << std::endl;
- boost::shared_ptr sp(t.shared_from_this());
-/*==========================================================================*|
- std::cout << "Capturing weak_ptr" << std::endl;
- boost::weak_ptr wp(sp);
-|*==========================================================================*/
- std::cout << "Tracking shared__ptr" << std::endl;
- mListener.track(sp);
- }
-#endif
-
- // non-pointer variant
- template
- void maybe_get_pointer(const T& t, truth) const
- {
- // Take the address of this object, because the object itself may be
- // trackable
-// add_if_trackable(boost::addressof(t));
- }
-
-/*==========================================================================*|
- // add_if_trackable() adds LLEventTrackable objects to mTrackables
- inline void add_if_trackable(const LLEventTrackable* t) const
- {
- if (t)
- {
- }
- }
-
- // pointer to anything not an LLEventTrackable subclass
- inline void add_if_trackable(const void*) const
- {
- }
-
- // pointer to free function
- // The following construct uses the preprocessor to generate
- // add_if_trackable() overloads accepting pointer-to-function taking
- // 0, 1, ..., LLEVENTS_LISTENER_ARITY parameters of arbitrary type.
-#define BOOST_PP_LOCAL_MACRO(n) \
- template \
- inline void \
- add_if_trackable(R (*)(BOOST_PP_ENUM_PARAMS(n, T))) const \
- { \
- }
-#define BOOST_PP_LOCAL_LIMITS (0, LLEVENTS_LISTENER_ARITY)
-#include BOOST_PP_LOCAL_ITERATE()
-#undef BOOST_PP_LOCAL_MACRO
-#undef BOOST_PP_LOCAL_LIMITS
-|*==========================================================================*/
-
- /// Bind a reference to the LLEventListener to call its track() method.
- LLEventListener& mListener;
- };
-
- /**
- * Utility template function to use Visitor appropriately
- *
- * @param raw_listener Callable to connect, typically a boost::bind()
- * expression. This will be visited by Visitor using boost::visit_each().
- * @param connect_funct Callable that will connect() @a raw_listener to an
- * LLStandardSignal, returning LLBoundListener.
- */
- template
- LLBoundListener visit_and_connect(const std::string& name,
- const LISTENER& raw_listener,
- const ConnectFunc& connect_func)
- {
- // Capture the listener
- LLEventListener listener(raw_listener);
- // Define our Visitor, binding the listener so we can call
- // listener.track() if we discover any shared_ptr.
- LLEventDetail::Visitor visitor(listener);
- // Allow unqualified visit_each() call for ADL
- using boost::visit_each;
- // Visit each component of a boost::bind() expression. Pass
- // 'raw_listener', our template argument, rather than 'listener' from
- // which type details have been erased. unwrap() comes from
- // Boost.Signals, in case we were passed a boost::ref().
- visit_each(visitor, LLEventDetail::unwrap(raw_listener));
- // Make the connection using passed function.
- LLBoundListener connection(connect_func(listener));
- // If the LISTENER is an LLListenerWrapperBase subclass, pass it the
- // desired information. It's important that we pass the raw_listener
- // so the compiler can make decisions based on its original type.
- const LLListenerWrapperBase* lwb =
- ll_template_cast(&raw_listener);
- if (lwb)
- {
- lwb->accept_name(name);
- lwb->accept_connection(connection);
- }
- // In any case, show new connection to caller.
- return connection;
- }
-} // namespace LLEventDetail
-
// Somewhat to my surprise, passing boost::bind(...boost::weak_ptr...) to
// listen() fails in Boost code trying to instantiate LLEventListener (i.e.
// LLStandardSignal::slot_type) because the boost::get_pointer() utility function isn't
@@ -1142,12 +755,4 @@ namespace boost
T* get_pointer(const weak_ptr& ptr) { return shared_ptr(ptr).get(); }
}
-/// Since we forbid use of listen(boost::bind(...shared_ptr...)), provide an
-/// easy way to cast to the corresponding weak_ptr.
-template
-boost::weak_ptr weaken(const boost::shared_ptr& ptr)
-{
- return boost::weak_ptr(ptr);
-}
-
#endif /* ! defined(LL_LLEVENTS_H) */
diff --git a/indra/llcommon/lleventtimer.cpp b/indra/llcommon/lleventtimer.cpp
index 0d96e03da4..f575a7b6bf 100644
--- a/indra/llcommon/lleventtimer.cpp
+++ b/indra/llcommon/lleventtimer.cpp
@@ -57,29 +57,17 @@ LLEventTimer::~LLEventTimer()
//static
void LLEventTimer::updateClass()
{
- std::list completed_timers;
- for (instance_iter iter = beginInstances(); iter != endInstances(); )
+ for (auto& timer : instance_snapshot())
{
- LLEventTimer& timer = *iter++;
F32 et = timer.mEventTimer.getElapsedTimeF32();
if (timer.mEventTimer.getStarted() && et > timer.mPeriod) {
timer.mEventTimer.reset();
if ( timer.tick() )
{
- completed_timers.push_back( &timer );
+ delete &timer;
}
}
}
-
- if ( completed_timers.size() > 0 )
- {
- for (std::list::iterator completed_iter = completed_timers.begin();
- completed_iter != completed_timers.end();
- completed_iter++ )
- {
- delete *completed_iter;
- }
- }
}
diff --git a/indra/llcommon/lleventtimer.h b/indra/llcommon/lleventtimer.h
index dc918121e1..dbbfe0c6e6 100644
--- a/indra/llcommon/lleventtimer.h
+++ b/indra/llcommon/lleventtimer.h
@@ -40,16 +40,83 @@ public:
LLEventTimer(F32 period); // period is the amount of time between each call to tick() in seconds
LLEventTimer(const LLDate& time);
virtual ~LLEventTimer();
-
+
//function to be called at the supplied frequency
// Normally return FALSE; TRUE will delete the timer after the function returns.
virtual BOOL tick() = 0;
static void updateClass();
+ /// Schedule recurring calls to generic callable every period seconds.
+ /// Returns a pointer; if you delete it, cancels the recurring calls.
+ template
+ static LLEventTimer* run_every(F32 period, const CALLABLE& callable);
+
+ /// Schedule a future call to generic callable. Returns a pointer.
+ /// CAUTION: The object referenced by that pointer WILL BE DELETED once
+ /// the callback has been called! LLEventTimer::getInstance(pointer) (NOT
+ /// pointer->getInstance(pointer)!) can be used to test whether the
+ /// pointer is still valid. If it is, deleting it will cancel the
+ /// callback.
+ template
+ static LLEventTimer* run_at(const LLDate& time, const CALLABLE& callable);
+
+ /// Like run_at(), but after a time delta rather than at a timestamp.
+ /// Same CAUTION.
+ template
+ static LLEventTimer* run_after(F32 interval, const CALLABLE& callable);
+
protected:
LLTimer mEventTimer;
F32 mPeriod;
+
+private:
+ template
+ class Generic;
};
+template
+class LLEventTimer::Generic: public LLEventTimer
+{
+public:
+ // making TIME generic allows engaging either LLEventTimer constructor
+ template
+ Generic(const TIME& time, bool once, const CALLABLE& callable):
+ LLEventTimer(time),
+ mOnce(once),
+ mCallable(callable)
+ {}
+ BOOL tick() override
+ {
+ mCallable();
+ // true tells updateClass() to delete this instance
+ return mOnce;
+ }
+
+private:
+ bool mOnce;
+ CALLABLE mCallable;
+};
+
+template
+LLEventTimer* LLEventTimer::run_every(F32 period, const CALLABLE& callable)
+{
+ // return false to schedule recurring calls
+ return new Generic(period, false, callable);
+}
+
+template
+LLEventTimer* LLEventTimer::run_at(const LLDate& time, const CALLABLE& callable)
+{
+ // return true for one-shot callback
+ return new Generic(time, true, callable);
+}
+
+template
+LLEventTimer* LLEventTimer::run_after(F32 interval, const CALLABLE& callable)
+{
+ // one-shot callback after specified interval
+ return new Generic(interval, true, callable);
+}
+
#endif //LL_EVENTTIMER_H
diff --git a/indra/llcommon/llexception.cpp b/indra/llcommon/llexception.cpp
index b32ec2c9c9..5ce8958687 100644
--- a/indra/llcommon/llexception.cpp
+++ b/indra/llcommon/llexception.cpp
@@ -18,10 +18,28 @@
#include
// external library headers
#include
+#include
+// On Mac, got:
+// #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define
+// `_GNU_SOURCE` macro or `BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED` if
+// _Unwind_Backtrace is available without `_GNU_SOURCE`."
+#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED
+#if LL_WINDOWS
+// On Windows, header-only implementation causes macro collisions -- use
+// prebuilt library
+#define BOOST_STACKTRACE_LINK
+#endif // LL_WINDOWS
+#include
// other Linden headers
#include "llerror.h"
#include "llerrorcontrol.h"
+// used to attach and extract stacktrace information to/from boost::exception,
+// see https://www.boost.org/doc/libs/release/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.exceptions_with_stacktrace
+// apparently the struct passed as the first template param needs no definition?
+typedef boost::error_info
+ errinfo_stacktrace;
+
namespace {
// used by crash_on_unhandled_exception_() and log_unhandled_exception_()
void log_unhandled_exception_(LLError::ELevel level,
@@ -53,3 +71,17 @@ void log_unhandled_exception_(const char* file, int line, const char* pretty_fun
// routinely, but we DO expect to return from this function.
log_unhandled_exception_(LLError::LEVEL_WARN, file, line, pretty_function, context);
}
+
+void annotate_exception_(boost::exception& exc)
+{
+ // https://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_transporting_data.html
+ // "Adding of Arbitrary Data to Active Exception Objects"
+ // Given a boost::exception&, we can add boost::error_info items to it
+ // without knowing its leaf type.
+ // The stacktrace constructor that lets us skip a level -- and why would
+ // we always include annotate_exception_()? -- also requires a max depth.
+ // For the nullary constructor, the stacktrace class declaration itself
+ // passes static_cast(-1), but that's kind of dubious.
+ // Anyway, which of us is really going to examine more than 100 frames?
+ exc << errinfo_stacktrace(boost::stacktrace::stacktrace(1, 100));
+}
diff --git a/indra/llcommon/llexception.h b/indra/llcommon/llexception.h
index dfcb7c192f..422dd8810a 100644
--- a/indra/llcommon/llexception.h
+++ b/indra/llcommon/llexception.h
@@ -67,9 +67,29 @@ struct LLContinueError: public LLException
* enriches the exception's diagnostic_information() with the source file,
* line and containing function of the LLTHROW() macro.
*/
-// Currently we implement that using BOOST_THROW_EXCEPTION(). Wrap it in
-// LLTHROW() in case we ever want to revisit that implementation decision.
-#define LLTHROW(x) BOOST_THROW_EXCEPTION(x)
+#define LLTHROW(x) \
+do { \
+ /* Capture the exception object 'x' by value. (Exceptions must */ \
+ /* be copyable.) It might seem simpler to use */ \
+ /* BOOST_THROW_EXCEPTION(annotate_exception_(x)) instead of */ \
+ /* three separate statements, but: */ \
+ /* - We want to throw 'x' with its original type, not just a */ \
+ /* reference to boost::exception. */ \
+ /* - To return x's original type, annotate_exception_() would */ \
+ /* have to be a template function. */ \
+ /* - We want annotate_exception_() to be opaque. */ \
+ /* We also might consider embedding BOOST_THROW_EXCEPTION() in */ \
+ /* our helper function, but we want the filename and line info */ \
+ /* embedded by BOOST_THROW_EXCEPTION() to be the throw point */ \
+ /* rather than always indicating the same line in */ \
+ /* llexception.cpp. */ \
+ auto exc{x}; \
+ annotate_exception_(exc); \
+ BOOST_THROW_EXCEPTION(exc); \
+ /* Use the classic 'do { ... } while (0)' macro trick to wrap */ \
+ /* our multiple statements. */ \
+} while (0)
+void annotate_exception_(boost::exception& exc);
/// Call this macro from a catch (...) clause
#define CRASH_ON_UNHANDLED_EXCEPTION(CONTEXT) \
diff --git a/indra/llcommon/llfasttimer.cpp b/indra/llcommon/llfasttimer.cpp
index 3d28cd15b0..08ea668964 100644
--- a/indra/llcommon/llfasttimer.cpp
+++ b/indra/llcommon/llfasttimer.cpp
@@ -193,27 +193,26 @@ TimeBlockTreeNode& BlockTimerStatHandle::getTreeNode() const
void BlockTimer::bootstrapTimerTree()
{
- for (BlockTimerStatHandle::instance_tracker_t::instance_iter it = BlockTimerStatHandle::instance_tracker_t::beginInstances(), end_it = BlockTimerStatHandle::instance_tracker_t::endInstances();
- it != end_it;
- ++it)
+ for (auto& base : BlockTimerStatHandle::instance_snapshot())
{
- BlockTimerStatHandle& timer = static_cast(*it);
+ // because of indirect derivation from LLInstanceTracker, have to downcast
+ BlockTimerStatHandle& timer = static_cast(base);
if (&timer == &BlockTimer::getRootTimeBlock()) continue;
// bootstrap tree construction by attaching to last timer to be on stack
// when this timer was called
if (timer.getParent() == &BlockTimer::getRootTimeBlock())
-{
+ {
TimeBlockAccumulator& accumulator = timer.getCurrentAccumulator();
if (accumulator.mLastCaller)
- {
+ {
timer.setParent(accumulator.mLastCaller);
accumulator.mParent = accumulator.mLastCaller;
- }
+ }
// no need to push up tree on first use, flag can be set spuriously
accumulator.mMoveUpTree = false;
- }
+ }
}
}
@@ -306,12 +305,10 @@ void BlockTimer::processTimes()
updateTimes();
// reset for next frame
- for (BlockTimerStatHandle::instance_tracker_t::instance_iter it = BlockTimerStatHandle::instance_tracker_t::beginInstances(),
- end_it = BlockTimerStatHandle::instance_tracker_t::endInstances();
- it != end_it;
- ++it)
+ for (auto& base : BlockTimerStatHandle::instance_snapshot())
{
- BlockTimerStatHandle& timer = static_cast(*it);
+ // because of indirect derivation from LLInstanceTracker, have to downcast
+ BlockTimerStatHandle& timer = static_cast(base);
TimeBlockAccumulator& accumulator = timer.getCurrentAccumulator();
accumulator.mLastCaller = NULL;
@@ -362,12 +359,10 @@ void BlockTimer::logStats()
LLSD sd;
{
- for (BlockTimerStatHandle::instance_tracker_t::instance_iter it = BlockTimerStatHandle::instance_tracker_t::beginInstances(),
- end_it = BlockTimerStatHandle::instance_tracker_t::endInstances();
- it != end_it;
- ++it)
+ for (auto& base : BlockTimerStatHandle::instance_snapshot())
{
- BlockTimerStatHandle& timer = static_cast(*it);
+ // because of indirect derivation from LLInstanceTracker, have to downcast
+ BlockTimerStatHandle& timer = static_cast(base);
LLTrace::PeriodicRecording& frame_recording = LLTrace::get_frame_recording();
sd[timer.getName()]["Time"] = (LLSD::Real) (frame_recording.getLastRecording().getSum(timer).value());
sd[timer.getName()]["Calls"] = (LLSD::Integer) (frame_recording.getLastRecording().getSum(timer.callCount()));
diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h
index d463fc9d65..5628a05b00 100644
--- a/indra/llcommon/llfasttimer.h
+++ b/indra/llcommon/llfasttimer.h
@@ -31,6 +31,10 @@
#include "lltrace.h"
#include "lltreeiterators.h"
+#if LL_WINDOWS
+#include
+#endif
+
#define LL_FAST_TIMER_ON 1
#define LL_FASTTIMER_USE_RDTSC 1
@@ -85,6 +89,8 @@ public:
// return __rdtsc();
//}
+
+
// shift off lower 8 bits for lower resolution but longer term timing
// on 1Ghz machine, a 32-bit word will hold ~1000 seconds of timing
#if LL_FASTTIMER_USE_RDTSC
diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h
index 398938b729..9de095b45d 100644
--- a/indra/llcommon/llfile.h
+++ b/indra/llcommon/llfile.h
@@ -86,6 +86,69 @@ public:
static const char * tmpdir();
};
+/// RAII class
+class LLUniqueFile
+{
+public:
+ // empty
+ LLUniqueFile(): mFileHandle(nullptr) {}
+ // wrap (e.g.) result of LLFile::fopen()
+ LLUniqueFile(LLFILE* f): mFileHandle(f) {}
+ // no copy
+ LLUniqueFile(const LLUniqueFile&) = delete;
+ // move construction
+ LLUniqueFile(LLUniqueFile&& other)
+ {
+ mFileHandle = other.mFileHandle;
+ other.mFileHandle = nullptr;
+ }
+ // The point of LLUniqueFile is to close on destruction.
+ ~LLUniqueFile()
+ {
+ close();
+ }
+
+ // simple assignment
+ LLUniqueFile& operator=(LLFILE* f)
+ {
+ close();
+ mFileHandle = f;
+ return *this;
+ }
+ // copy assignment deleted
+ LLUniqueFile& operator=(const LLUniqueFile&) = delete;
+ // move assignment
+ LLUniqueFile& operator=(LLUniqueFile&& other)
+ {
+ close();
+ std::swap(mFileHandle, other.mFileHandle);
+ return *this;
+ }
+
+ // explicit close operation
+ void close()
+ {
+ if (mFileHandle)
+ {
+ // in case close() throws, set mFileHandle null FIRST
+ LLFILE* h{nullptr};
+ std::swap(h, mFileHandle);
+ LLFile::close(h);
+ }
+ }
+
+ // detect whether the wrapped LLFILE is open or not
+ explicit operator bool() const { return bool(mFileHandle); }
+ bool operator!() { return ! mFileHandle; }
+
+ // LLUniqueFile should be usable for any operation that accepts LLFILE*
+ // (or FILE* for that matter)
+ operator LLFILE*() const { return mFileHandle; }
+
+private:
+ LLFILE* mFileHandle;
+};
+
#if LL_WINDOWS
/**
* @brief Controlling input for files.
diff --git a/indra/llcommon/llinstancetracker.cpp b/indra/llcommon/llinstancetracker.cpp
index 3f990f4869..e7193b70b5 100644
--- a/indra/llcommon/llinstancetracker.cpp
+++ b/indra/llcommon/llinstancetracker.cpp
@@ -27,25 +27,15 @@
#include "linden_common.h"
// associated header
#include "llinstancetracker.h"
-#include "llapr.h"
-
+#include "llerror.h"
// STL headers
// std headers
// external library headers
// other Linden headers
-void LLInstanceTrackerBase::StaticBase::incrementDepth()
+void LLInstanceTrackerPrivate::logerrs(const char* cls, const std::string& arg1,
+ const std::string& arg2, const std::string& arg3)
{
- ++sIterationNestDepth;
-}
-
-void LLInstanceTrackerBase::StaticBase::decrementDepth()
-{
- llassert(sIterationNestDepth);
- --sIterationNestDepth;
-}
-
-U32 LLInstanceTrackerBase::StaticBase::getDepth()
-{
- return sIterationNestDepth;
+ LL_ERRS("LLInstanceTracker") << LLError::Log::demangle(cls)
+ << arg1 << arg2 << arg3 << LL_ENDL;
}
diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h
index 363d0bcbd5..402333cca7 100644
--- a/indra/llcommon/llinstancetracker.h
+++ b/indra/llcommon/llinstancetracker.h
@@ -28,354 +28,432 @@
#ifndef LL_LLINSTANCETRACKER_H
#define LL_LLINSTANCETRACKER_H
-#include
#include
+#include
+#include