← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/remove-txlongpoll into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/remove-txlongpoll into lp:launchpad.

Commit message:
Remove txlongpoll.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/remove-txlongpoll/+merge/366122

The Launchpad integration for this was never fully rolled out, is broken in some hard-to-fix ways, and is unlikely to be fixed.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/remove-txlongpoll into lp:launchpad.
=== modified file 'Makefile'
--- Makefile	2018-06-06 12:46:56 +0000
+++ Makefile	2019-04-16 14:35:10 +0000
@@ -284,7 +284,7 @@
 	bin/test -f $(TESTFLAGS) $(TESTOPTS)
 
 run: build inplace stop
-	bin/run -r librarian,bing-webservice,memcached,rabbitmq,txlongpoll \
+	bin/run -r librarian,bing-webservice,memcached,rabbitmq \
 	-i $(LPCONFIG)
 
 run-testapp: LPCONFIG=testrunner-appserver
@@ -303,7 +303,7 @@
 run_all: build inplace stop
 	bin/run \
 	 -r librarian,sftp,forker,mailman,codebrowse,bing-webservice,\
-	memcached,rabbitmq,txlongpoll -i $(LPCONFIG)
+	memcached,rabbitmq -i $(LPCONFIG)
 
 run_codebrowse: compile
 	BZR_PLUGIN_PATH=bzrplugins $(PY) scripts/start-loggerhead.py

=== modified file 'configs/development/launchpad-lazr.conf'
--- configs/development/launchpad-lazr.conf	2018-06-14 16:28:45 +0000
+++ configs/development/launchpad-lazr.conf	2019-04-16 14:35:10 +0000
@@ -193,11 +193,6 @@
 store_search_url: https://api.snapcraft.io/
 tools_source: deb http://ppa.launchpad.net/snappy-dev/snapcraft-daily/ubuntu %(series)s main
 
-[txlongpoll]
-launch: True
-frontend_port: 22435
-uri: /+longpoll/
-
 [rosetta]
 global_suggestions_enabled: True
 generate_templates: True

=== modified file 'configs/development/local-launchpad-apache'
--- configs/development/local-launchpad-apache	2017-01-10 17:26:29 +0000
+++ configs/development/local-launchpad-apache	2019-04-16 14:35:10 +0000
@@ -32,7 +32,6 @@
   SSLCertificateKeyFile /etc/apache2/ssl/launchpad.key
 
   ProxyPreserveHost on
-  ProxyPass /+longpoll/ http://localhost:22435/ retry=1
   ProxyPass /+combo !
   ProxyPass / http://localhost:8086/ retry=1
 

=== modified file 'configs/testrunner/launchpad-lazr.conf'
--- configs/testrunner/launchpad-lazr.conf	2018-05-22 07:53:52 +0000
+++ configs/testrunner/launchpad-lazr.conf	2019-04-16 14:35:10 +0000
@@ -164,11 +164,6 @@
 password: none
 virtual_host: none
 
-[txlongpoll]
-launch: False
-frontend_port: none
-uri: none
-
 [rosetta]
 generate_templates: True
 

=== modified file 'constraints.txt'
--- constraints.txt	2019-04-08 07:13:18 +0000
+++ constraints.txt	2019-04-16 14:35:10 +0000
@@ -365,8 +365,6 @@
 Twisted[conch,tls]==18.4.0
 txAMQP==0.6.2
 txfixtures==0.4.2
-txlongpoll==0.2.12
-txlongpollfixture==0.1.3
 txpkgupload==0.2
 typing==3.6.2
 unittest2==1.1.0

=== modified file 'database/sampledata/current-dev.sql'
--- database/sampledata/current-dev.sql	2017-12-15 12:15:25 +0000
+++ database/sampledata/current-dev.sql	2019-04-16 14:35:10 +0000
@@ -1,4 +1,4 @@
--- Copyright 2010-2017 Canonical Ltd.  This software is licensed under the
+-- Copyright 2010-2019 Canonical Ltd.  This software is licensed under the
 -- GNU Affero General Public License version 3 (see the file LICENSE).
 -- Created using pg_dump (PostgreSQL) 9.3.5
 
@@ -3825,7 +3825,6 @@
 ALTER TABLE featureflag DISABLE TRIGGER ALL;
 
 INSERT INTO featureflag (scope, priority, flag, value, date_modified) VALUES ('default', 0, 'js.combo_loader.enabled', 'true', '2012-05-18 07:34:39.239649');
-INSERT INTO featureflag (scope, priority, flag, value, date_modified) VALUES ('default', 1, 'longpoll.merge_proposals.enabled', 'true', '2012-05-18 07:34:39.239649');
 
 
 ALTER TABLE featureflag ENABLE TRIGGER ALL;
@@ -3833,7 +3832,6 @@
 
 ALTER TABLE featureflagchangelogentry DISABLE TRIGGER ALL;
 
-INSERT INTO featureflagchangelogentry (id, date_changed, diff, comment, person) VALUES (1, '2011-10-06 12:44:04.37357', '+longpoll.merge_proposals.enabled	default	1	true', 'Enable long-poll for merge proposals in development.', 16);
 
 
 ALTER TABLE featureflagchangelogentry ENABLE TRIGGER ALL;

=== removed file 'lib/lp/app/javascript/longpoll.js'
--- lib/lp/app/javascript/longpoll.js	2017-07-20 13:29:41 +0000
+++ lib/lp/app/javascript/longpoll.js	1970-01-01 00:00:00 +0000
@@ -1,217 +0,0 @@
-/* Copyright 2011 Canonical Ltd.  This software is licensed under the
- * GNU Affero General Public License version 3 (see the file LICENSE).
- *
- * The Launchpad Longpoll module provides the functionnality to deal
- * with longpolling on the JavaScript side.
- *
- * The module method setupLongPollManager is called in every template and
- * the long poll machinery will only be started if LP.cache.longpoll
- * is populated.
- *
- * Usually the only thing you will want to do to use the long polling feature
- * is:
- *
- * a) to make sure LP.cache.longpoll is populated with 'key' and 'uri'.
- *
- * b) to create Javascript handlers for the events which will be fired:
- *    - event_key will be fired when the event on the server side is triggered
- *    (event_key being the name of the event on the server side).
- *    - see below for other events fired by the longpoll machinery.
- *
- * @module longpoll
- */
-YUI.add('lp.app.longpoll', function(Y) {
-
-var namespace = Y.namespace('lp.app.longpoll');
-
-// Event fired when the long polling request starts.
-namespace.longpoll_start_event = 'lp.app.longpoll.start';
-
-// Event fired each time the long polling request fails (to connect or
-// to parse the returned result).
-namespace.longpoll_fail_event = 'lp.app.longpoll.failure';
-
-// Event fired when the delay between each failed connection is set to
-// a long delay (after MAX_SHORT_DELAY_FAILED_ATTEMPTS failed attempts).
-namespace.longpoll_longdelay = 'lp.app.longpoll.longdelay';
-
-// Event fired when the delay between each failed connection is set back
-// to a short delay.
-namespace.longpoll_shortdelay = 'lp.app.longpoll.shortdelay';
-
-namespace._manager = null;
-
-// After MAX_SHORT_DELAY_FAILED_ATTEMPTS failed connections (real failed
-// connections or connection getting an invalid return) separated
-// by SHORT_DELAY (millisec), wait LONG_DELAY (millisec) between
-// each failed connection.
-namespace.MAX_SHORT_DELAY_FAILED_ATTEMPTS = 5;
-namespace.SHORT_DELAY = 1000;
-namespace.LONG_DELAY = 3*60*1000;
-
-/**
- *
- * A Long Poll Manager creates and manages a long polling connexion
- * to the server to fetch events. This class is not directly used
- * but managed through 'setupLongPollManager' which creates and
- * initialises a singleton LongPollManager.
- *
- * @class LongPollManager
- */
-function LongPollManager(config) {
-    LongPollManager.superclass.constructor.apply(this, arguments);
-}
-
-LongPollManager.NAME = "longPollManager";
-
-Y.extend(LongPollManager, Y.Base, {
-    initializer : function(cfg) {
-        this._started = false;
-        this._failed_attempts = 0;
-        this._repoll = true;
-        this._sequence = 0;
-    },
-
-    setConnectionInfos : function(key, uri) {
-        this.key = key;
-        this.uri = uri;
-    },
-
-    _io : function (uri, config) {
-        Y.io(uri, config);
-    },
-
-    successPoll : function (id, response) {
-        try {
-            var data = Y.JSON.parse(response.responseText);
-            if (!data.hasOwnProperty('event_key')) {
-                throw new Error("Response has no event_key");
-            }
-            Y.fire(data.event_key, data);
-            return true;
-        }
-        catch (e) {
-            Y.fire(namespace.longpoll_fail_event, e);
-            return false;
-        }
-    },
-
-    failurePoll : function () {
-        Y.fire(namespace.longpoll_fail_event);
-    },
-
-    /**
-     * Return the delay (milliseconds) to wait before trying to reconnect
-     * again after a failed connection.
-     *
-     * The rationale here is that:
-     * 1. We should not try to reconnect instantaneously after a failed
-     *     connection.
-     * 2. After a certain number of failed connections, we should set the
-     *     delay between two failed connection to a bigger number because
-     *     the server may be having problems.
-     *
-     * @method _pollDelay
-     */
-    _pollDelay : function() {
-        this._failed_attempts = this._failed_attempts + 1;
-        if (this._failed_attempts >=
-                namespace.MAX_SHORT_DELAY_FAILED_ATTEMPTS) {
-            Y.fire(namespace.longpoll_longdelay);
-            return namespace.LONG_DELAY;
-        }
-        else {
-            return namespace.SHORT_DELAY;
-        }
-    },
-
-    /**
-     * Relaunch a connection to the server after a successful or
-     * a failed connection.
-     *
-     * @method repoll
-     * @param {Boolean} failed: whether or not the previous connection
-     *     has failed.
-     */
-    repoll : function(failed) {
-        if (!failed) {
-            if (this._failed_attempts >=
-                    namespace.MAX_SHORT_DELAY_FAILED_ATTEMPTS) {
-                Y.fire(namespace.longpoll_shortdelay);
-            }
-            this._failed_attempts = 0;
-            if (this._repoll) {
-                this.poll();
-            }
-        }
-        else {
-            var delay = this._pollDelay();
-            if (this._repoll) {
-                Y.later(delay, this, this.poll);
-            }
-        }
-    },
-
-    poll : function() {
-        var that = this;
-        var config = {
-            method: "GET",
-            sync: false,
-            on: {
-                failure: function(id, response) {
-                    if (Y.Lang.isValue(response) &&
-                        Y.Lang.isValue(response.status) &&
-                        (response.status === 408 ||
-                         response.status === 504)) {
-                        // If the error code is:
-                        // - 408 Request timeout
-                        // - 504 Gateway timeout
-                        // Then ignore the error and start
-                        // polling again.
-                        that.repoll(false);
-                    }
-                    else {
-                        that.failurePoll();
-                        that.repoll(true);
-                    }
-                },
-                success: function(id, response) {
-                    var res = that.successPoll(id, response);
-                    that.repoll(res);
-                }
-            }
-        };
-        this._sequence = this._sequence + 1;
-        var queue_uri = this.uri +
-            "?uuid=" + this.key +
-            "&sequence=" + this._sequence;
-        if (!this._started) {
-            Y.fire(namespace.longpoll_start_event);
-            this._started = true;
-        }
-        this._io(queue_uri, config);
-    }
-});
-
-namespace.LongPollManager = LongPollManager;
-
-namespace.getLongPollManager = function() {
-    if (!Y.Lang.isValue(namespace._manager)) {
-        namespace._manager = new namespace.LongPollManager();
-    }
-    return namespace._manager;
-};
-
-namespace.setupLongPollManager = function() {
-    if (Y.Object.owns(LP.cache, 'longpoll') &&
-            Y.Lang.isValue(LP.cache.longpoll)) {
-        var key = LP.cache.longpoll.key;
-        var uri = LP.cache.longpoll.uri;
-        var longpollmanager = namespace.getLongPollManager();
-        longpollmanager.setConnectionInfos(key, uri);
-        longpollmanager.poll();
-        return longpollmanager;
-    }
-};
-
-}, "0.1", {"requires":["base", "event", "json", "io"]});

=== removed file 'lib/lp/app/javascript/tests/test_longpoll.html'
--- lib/lp/app/javascript/tests/test_longpoll.html	2012-10-26 09:54:28 +0000
+++ lib/lp/app/javascript/tests/test_longpoll.html	1970-01-01 00:00:00 +0000
@@ -1,47 +0,0 @@
-<!DOCTYPE html>
-<!--
-Copyright 2012 Canonical Ltd.  This software is licensed under the
-GNU Affero General Public License version 3 (see the file LICENSE).
--->
-
-<html>
-  <head>
-      <title>Test longpoll</title>
-
-      <!-- YUI and test setup -->
-      <script type="text/javascript"
-              src="../../../../../build/js/yui/yui/yui.js">
-      </script>
-      <link rel="stylesheet"
-      href="../../../../../build/js/yui/console/assets/console-core.css" />
-      <link rel="stylesheet"
-      href="../../../../../build/js/yui/test-console/assets/skins/sam/test-console.css" />
-      <link rel="stylesheet"
-      href="../../../../../build/js/yui/test/assets/skins/sam/test.css" />
-
-      <script type="text/javascript"
-              src="../../../../../build/js/lp/app/testing/testrunner.js"></script>
-
-      <link rel="stylesheet" href="../../../app/javascript/testing/test.css" />
-
-      <!-- Dependencies -->
-      <script type="text/javascript"
-          src="../../../../../build/js/lp/app/client.js"></script>
-
-      <!-- The module under test. -->
-      <script type="text/javascript" src="../longpoll.js"></script>
-
-      <!-- Placeholder for any css asset for this module. -->
-      <!-- <link rel="stylesheet" href="../assets/longpoll-core.css" /> -->
-
-      <!-- The test suite. -->
-      <script type="text/javascript" src="test_longpoll.js"></script>
-
-    </head>
-    <body class="yui3-skin-sam">
-        <ul id="suites">
-            <!-- <li>lp.large_indicator.test</li> -->
-            <li>lp.longpoll.test</li>
-        </ul>
-    </body>
-</html>

=== removed file 'lib/lp/app/javascript/tests/test_longpoll.js'
--- lib/lp/app/javascript/tests/test_longpoll.js	2017-07-24 15:37:03 +0000
+++ lib/lp/app/javascript/tests/test_longpoll.js	1970-01-01 00:00:00 +0000
@@ -1,283 +0,0 @@
-/* Copyright 2011 Canonical Ltd.  This software is licensed under the
- * GNU Affero General Public License version 3 (see the file LICENSE). */
-YUI.add('lp.longpoll.test', function (Y) {
-    var longpoll = Y.lp.app.longpoll;
-
-    var tests = Y.namespace('lp.longpoll.test');
-    tests.suite = new Y.Test.Suite('longpoll Tests');
-
-    tests.suite.add(new Y.Test.Case({
-        name: 'TestLongPollSingleton',
-        tearDown: function() {
-            // Cleanup the singleton;
-            longpoll._manager = null;
-        },
-
-        testGetSingletonLongPollManager: function() {
-            Y.Assert.isNull(longpoll._manager);
-            var manager = longpoll.getLongPollManager();
-            Y.Assert.isNotNull(longpoll._manager);
-            var manager2 = longpoll.getLongPollManager();
-            Y.Assert.areSame(manager, manager2);
-        },
-
-        testInitLongPollManagerNoLongPoll: function() {
-            // if LP.cache.longpoll.key is undefined: no longpoll manager
-            // is created by setupLongPollManager.
-            window.LP = {
-                links: {},
-                cache: {}
-            };
-
-            longpoll.setupLongPollManager(true);
-            Y.Assert.isNull(longpoll._manager);
-        },
-
-        testInitLongPollManagerLongPoll: function() {
-            window.LP = {
-                links: {},
-                cache: {
-                    longpoll: {
-                        key: 'key',
-                        uri: '/+longpoll/'
-                    }
-                }
-            };
-
-            longpoll.setupLongPollManager(true);
-            Y.Assert.isNotNull(longpoll._manager);
-        }
-    }));
-
-    tests.suite.add(new Y.Test.Case({
-        name: 'TestLongPoll',
-
-        setUp: function() {
-            var manager = longpoll.getLongPollManager();
-            manager._repoll = false;
-            this.createBaseLP();
-        },
-
-        tearDown: function() {
-            // Cleanup the singleton;
-            longpoll._manager = null;
-        },
-
-        createBaseLP:function() {
-            window.LP = {
-                links: {},
-                cache: {}
-            };
-        },
-
-        setupLPCache: function() {
-            LP.cache.longpoll = {
-                key: 'key',
-                uri: '/+longpoll/'
-            };
-        },
-
-       setupLongPoll: function(nb_calls) {
-            this.setupLPCache();
-            return longpoll.setupLongPollManager(true);
-        },
-
-        testInitLongPollManagerQueueName: function() {
-            var manager = this.setupLongPoll();
-            Y.Assert.areEqual(LP.cache.longpoll.key, manager.key);
-            Y.Assert.areEqual(LP.cache.longpoll.uri, manager.uri);
-            Y.Assert.isFalse(Y.Lang.isValue(manager.nb_calls));
-        },
-
-        testPollStarted: function() {
-            var fired = false;
-            Y.on(longpoll.longpoll_start_event, function() {
-                fired = true;
-            });
-            this.setupLongPoll();
-            Y.Assert.isTrue(fired, "Start event not fired.");
-        },
-
-        testPollFailure: function() {
-            var fired = false;
-            Y.on(longpoll.longpoll_fail_event, function() {
-                fired = true;
-            });
-            // Monkeypatch io to simulate failure.
-            var manager = longpoll.getLongPollManager();
-            manager._io = function(uri, config) {
-                config.on.failure();
-            };
-            this.setupLongPoll();
-            Y.Assert.isTrue(fired, "Failure event not fired.");
-        },
-
-        testSuccessPollInvalidData: function() {
-            var manager = longpoll.getLongPollManager();
-            var custom_response = "{{";
-            var response = {
-                responseText: custom_response
-            };
-            var res = manager.successPoll("2", response);
-            Y.Assert.isFalse(res);
-        },
-
-        testSuccessPollMalformedData: function() {
-            var manager = longpoll.getLongPollManager();
-            var response = {
-                responseText: '{ "something": "6" }'
-            };
-            var res = manager.successPoll("2", response);
-            Y.Assert.isFalse(res);
-         },
-
-         testSuccessPollWellformedData: function() {
-            var manager = longpoll.getLongPollManager();
-            var response = {
-                responseText: '{ "event_key": "4", "something": "6"}'
-            };
-            var res = manager.successPoll("2", response);
-            Y.Assert.isTrue(res);
-        },
-
-        testPollDelay: function() {
-            // Create event listeners.
-            var longdelay_event_fired = false;
-            Y.on(longpoll.longpoll_longdelay, function(data) {
-                longdelay_event_fired = true;
-            });
-            var shortdelay_event_fired = false;
-            Y.on(longpoll.longpoll_shortdelay, function(data) {
-                shortdelay_event_fired = true;
-            });
-            var manager = longpoll.getLongPollManager();
-            // Monkeypatch io to simulate failure.
-            manager._io = function(uri, config) {
-                config.on.failure();
-            };
-            Y.Assert.areEqual(0, manager._failed_attempts);
-            this.setupLongPoll();
-            Y.Assert.areEqual(1, manager._failed_attempts);
-            var i, delay;
-            for (i=0; i<longpoll.MAX_SHORT_DELAY_FAILED_ATTEMPTS-2; i++) {
-                Y.Assert.areEqual(i+1, manager._failed_attempts);
-                delay = manager._pollDelay();
-                Y.Assert.areEqual(delay, longpoll.SHORT_DELAY);
-            }
-            // After MAX_SHORT_DELAY_FAILED_ATTEMPTS failed attempts, the
-            // delay returned by _pollDelay is LONG_DELAY and
-            // longpoll_longdelay is fired.
-            Y.Assert.isFalse(longdelay_event_fired);
-            delay = manager._pollDelay();
-            Y.Assert.isTrue(longdelay_event_fired);
-            Y.Assert.areEqual(delay, longpoll.LONG_DELAY);
-
-            // Monkeypatch io to simulate success.
-            manager._io = function(uri, config) {
-                config.on.success();
-            };
-            // After a success, longpoll.longpoll_shortdelay is fired.
-            Y.Assert.isFalse(shortdelay_event_fired);
-            delay = manager.poll();
-            Y.Assert.isTrue(shortdelay_event_fired);
-        },
-
-        testPollUriSequence: function() {
-            // Each new polling increases the sequence parameter:
-            // /+longpoll/?uuid=key&sequence=1
-            // /+longpoll/?uuid=key&sequence=2
-            // /+longpoll/?uuid=key&sequence=3
-            // ..
-            var count = 0;
-            // Monkeypatch io to simulate failure.
-            var manager = longpoll.getLongPollManager();
-            manager._io = function(uri, config) {
-                Y.Assert.areEqual(
-                    '/+longpoll/?uuid=key&sequence=' + (count+1),
-                    uri);
-                count = count + 1;
-                var response = {
-                   responseText: '{"i":2}'
-                };
-                config.on.success(2, response);
-            };
-            this.setupLongPoll();
-            var request;
-            for (request=1; request<10; request++) {
-                Y.Assert.isTrue(count === request, "Uri not requested.");
-                manager.poll();
-            }
-        },
-
-        _testDoesNotFail: function(error_code) {
-            // Assert that, when the longpoll request receives an error
-            // with code error_code, it is not treated as a failed
-            // connection attempt.
-            var manager = longpoll.getLongPollManager();
-            // Monkeypatch io to simulate a request timeout.
-            manager._io = function(uri, config) {
-                var response = {status: error_code};
-                config.on.failure(4, response);
-            };
-
-            Y.Assert.areEqual(0, manager._failed_attempts);
-            this.setupLongPoll();
-            Y.Assert.areEqual(0, manager._failed_attempts);
-        },
-
-        test408RequestTimeoutHandling: function() {
-            this._testDoesNotFail(408);
-        },
-
-        test504GatewayTimeoutHandling: function() {
-            this._testDoesNotFail(504);
-        },
-
-        testPollPayLoadBad: function() {
-            // If a non valid response is returned, longpoll_fail_event
-            // is fired.
-            var fired = false;
-            Y.on(longpoll.longpoll_fail_event, function() {
-                fired = true;
-            });
-            var manager = longpoll.getLongPollManager();
-            // Monkeypatch io.
-            manager._io = function(uri, config) {
-                var response = {
-                   responseText: "{non valid json"
-                };
-                config.on.success(2, response);
-            };
-            this.setupLongPoll();
-            Y.Assert.isTrue(fired, "Failure event not fired.");
-        },
-
-        testPollPayLoadOk: function() {
-            // Create a valid message.
-            var custom_response = {
-                'event_key': 'my-event',
-                'something': {something_else: 1234}
-            };
-            var fired = false;
-            Y.on(custom_response.event_key, function(data) {
-                fired = true;
-                Y.Assert.areEqual(data, custom_response);
-            });
-            var manager = longpoll.getLongPollManager();
-            // Monkeypatch io.
-            manager._io = function(uri, config) {
-                var response = {
-                   responseText: Y.JSON.stringify(custom_response)
-                };
-                config.on.success(2, response);
-            };
-            this.setupLongPoll();
-            Y.Assert.isTrue(fired, "Custom event not fired.");
-        }
-    }));
-
-}, '0.1', {
-    requires: [
-        'lp.testing.runner', 'test', 'test-console', 'node-event-simulate',
-        'json', 'lp.app.longpoll']
-});

=== removed directory 'lib/lp/app/longpoll'
=== removed file 'lib/lp/app/longpoll/__init__.py'
--- lib/lp/app/longpoll/__init__.py	2012-02-21 22:46:28 +0000
+++ lib/lp/app/longpoll/__init__.py	1970-01-01 00:00:00 +0000
@@ -1,50 +0,0 @@
-# Copyright 2011-2012 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Long-poll infrastructure."""
-
-__metaclass__ = type
-__all__ = [
-    "emit",
-    "subscribe",
-    ]
-
-from lazr.restful.utils import get_current_browser_request
-from zope.component import getAdapter
-
-from lp.services.longpoll.interfaces import (
-    ILongPollEvent,
-    ILongPollSubscriber,
-    )
-
-
-def subscribe(target, event_name=u"", request=None):
-    """Convenience method to subscribe the current request.
-
-    :param target: Something that can be adapted to `ILongPollEvent`.
-    :param event_name: The name of the event to subscribe to. This is used to
-        look up a named adapter from `target` to `ILongPollEvent`.
-    :param request: The request for which to get an `ILongPollSubscriber`. It
-        a request is not specified the currently active request is used.
-    :return: The `ILongPollEvent` that has been subscribed to.
-    """
-    event = getAdapter(target, ILongPollEvent, name=event_name)
-    if request is None:
-        request = get_current_browser_request()
-    subscriber = ILongPollSubscriber(request)
-    subscriber.subscribe(event)
-    return event
-
-
-def emit(source, event_name=u"", **data):
-    """Convenience method to emit a message for an event.
-
-    :param source: Something that can be adapted to `ILongPollEvent`.
-    :param event_name: The name of the event to subscribe to. This is used to
-        look up a named adapter from `target` to `ILongPollEvent`.
-    :param data: See `ILongPollEvent.emit`.
-    :return: The `ILongPollEvent` that has been emitted.
-    """
-    event = getAdapter(source, ILongPollEvent, name=event_name)
-    event.emit(**data)
-    return event

=== removed directory 'lib/lp/app/longpoll/tests'
=== removed file 'lib/lp/app/longpoll/tests/__init__.py'
=== removed file 'lib/lp/app/longpoll/tests/test_longpoll.py'
--- lib/lp/app/longpoll/tests/test_longpoll.py	2015-07-09 12:18:51 +0000
+++ lib/lp/app/longpoll/tests/test_longpoll.py	1970-01-01 00:00:00 +0000
@@ -1,121 +0,0 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Tests for lp.app.longpoll."""
-
-__metaclass__ = type
-
-from zope.component import (
-    adapter,
-    getUtility,
-    )
-from zope.interface import (
-    Attribute,
-    implementer,
-    Interface,
-    )
-
-from lp.app.longpoll import (
-    emit,
-    subscribe,
-    )
-from lp.services.longpoll.interfaces import (
-    ILongPollEvent,
-    ILongPollSubscriber,
-    )
-from lp.services.messaging.interfaces import IMessageSession
-from lp.services.webapp.servers import LaunchpadTestRequest
-from lp.testing import TestCase
-from lp.testing.fixture import ZopeAdapterFixture
-from lp.testing.layers import LaunchpadFunctionalLayer
-
-
-class IFakeObject(Interface):
-
-    ident = Attribute("ident")
-
-
-@implementer(IFakeObject)
-class FakeObject:
-
-    def __init__(self, ident):
-        self.ident = ident
-
-
-@adapter(IFakeObject)
-@implementer(ILongPollEvent)
-class FakeEvent:
-
-    def __init__(self, source):
-        self.source = source
-
-    @property
-    def event_key(self):
-        return "event-key-%s" % self.source.ident
-
-    def emit(self, **data):
-        # Don't cargo-cult this; see .adapters.event.LongPollEvent instead.
-        session = getUtility(IMessageSession)
-        producer = session.getProducer(self.event_key)
-        producer.sendNow(data)
-
-
-class TestFunctions(TestCase):
-
-    layer = LaunchpadFunctionalLayer
-
-    def test_subscribe(self):
-        # subscribe() gets the ILongPollEvent for the given target and the
-        # ILongPollSubscriber for the given request (or the current request is
-        # discovered). It subscribes the latter to the event, then returns the
-        # event.
-        session = getUtility(IMessageSession)
-        request = LaunchpadTestRequest()
-        an_object = FakeObject(12345)
-        with ZopeAdapterFixture(FakeEvent):
-            event = subscribe(an_object, request=request)
-        self.assertIsInstance(event, FakeEvent)
-        self.assertEqual("event-key-12345", event.event_key)
-        session.flush()
-        # Emitting an event-key-12345 event will put something on the
-        # subscriber's queue.
-        event_data = {"1234": 5678}
-        event.emit(**event_data)
-        subscriber = ILongPollSubscriber(request)
-        subscribe_queue = session.getConsumer(subscriber.subscribe_key)
-        message = subscribe_queue.receive(timeout=5)
-        self.assertEqual(event_data, message)
-
-    def test_subscribe_to_named_event(self):
-        # When an event_name is given to subscribe(), a named adapter is used
-        # to get the ILongPollEvent for the given target.
-        request = LaunchpadTestRequest()
-        an_object = FakeObject(12345)
-        with ZopeAdapterFixture(FakeEvent, name="foo"):
-            event = subscribe(an_object, event_name="foo", request=request)
-        self.assertIsInstance(event, FakeEvent)
-
-    def test_emit(self):
-        # emit() gets the ILongPollEvent for the given target and passes the
-        # given data to its emit() method. It then returns the event.
-        an_object = FakeObject(12345)
-        with ZopeAdapterFixture(FakeEvent):
-            event = emit(an_object)
-            session = getUtility(IMessageSession)
-            producer = session.getProducer(event.event_key)
-            subscribe_queue = session.getConsumer("whatever")
-            producer.associateConsumerNow(subscribe_queue)
-            # Emit the event again; the subscribe queue was not associated
-            # with the event before now.
-            event_data = {"8765": 4321}
-            event = emit(an_object, **event_data)
-        message = subscribe_queue.receive(timeout=5)
-        self.assertEqual(event_data, message)
-
-    def test_emit_named_event(self):
-        # When an event_name is given to emit(), a named adapter is used to
-        # get the ILongPollEvent for the given target.
-        an_object = FakeObject(12345)
-        with ZopeAdapterFixture(FakeEvent, name="foo"):
-            event = emit(an_object, "foo")
-        self.assertIsInstance(event, FakeEvent)

=== modified file 'lib/lp/app/templates/base-layout-macros.pt'
--- lib/lp/app/templates/base-layout-macros.pt	2019-01-05 09:54:44 +0000
+++ lib/lp/app/templates/base-layout-macros.pt	2019-04-16 14:35:10 +0000
@@ -120,7 +120,7 @@
         //<![CDATA[
         LPJS.use('base', 'node', 'console', 'event',
             'oop', 'lp', 'lp.app.foldables','lp.app.sorttable',
-            'lp.app.inlinehelp', 'lp.app.links', 'lp.app.longpoll',
+            'lp.app.inlinehelp', 'lp.app.links',
             'lp.bugs.bugtask_index', 'lp.bugs.subscribers',
             'lp.app.ellipsis', 'lp.code.branchmergeproposal.diff',
             'lp.views.global',
@@ -135,14 +135,6 @@
                 Y.lp.activate_collapsibles();
                 Y.lp.app.foldables.activate();
                 Y.lp.app.links.check_valid_lp_links();
-                // Longpolling will only start if
-                // LP.cache.longpoll is populated.
-                // We use Y.later to work around a Safari/Chrome 'feature':
-                // The mouse cursor stays 'busy' until all the requests started during
-                // page load are finished.  Hence we want the long poll request to start
-                // right *after* the page has loaded.
-                Y.later(0, Y.lp.app.longpoll, Y.lp.app.longpoll.setupLongPollManager);
-
             });
 
             Y.on('lp:context:web_link:changed', function(e) {

=== modified file 'lib/lp/code/browser/branchmergeproposal.py'
--- lib/lp/code/browser/branchmergeproposal.py	2019-01-31 14:45:32 +0000
+++ lib/lp/code/browser/branchmergeproposal.py	2019-04-16 14:35:10 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Views, navigation and actions for BranchMergeProposals."""
@@ -69,7 +69,6 @@
     vocabulary_to_choice_edit_items,
     )
 from lp.app.browser.tales import DateTimeFormatterAPI
-from lp.app.longpoll import subscribe
 from lp.code.adapters.branch import BranchMergeProposalNoPreviewDiffDelta
 from lp.code.browser.codereviewcomment import CodeReviewDisplayComment
 from lp.code.browser.decorations import DecoratedBranch
@@ -628,9 +627,6 @@
         else:
             cache.objects['branch_diff_link'] = (
                 canonical_url(self.context.parent) + '/+diff/')
-        if getFeatureFlag("longpoll.merge_proposals.enabled"):
-            cache.objects['merge_proposal_event_key'] = subscribe(
-                self.context).event_key
 
     @action('Claim', name='claim')
     def claim_action(self, action, data):

=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py'
--- lib/lp/code/browser/tests/test_branchmergeproposal.py	2019-01-31 14:21:09 +0000
+++ lib/lp/code/browser/tests/test_branchmergeproposal.py	2019-04-16 14:35:10 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Unit tests for BranchMergeProposals."""
@@ -1576,26 +1576,6 @@
             git_api.notify(bmp.source_git_repository.getInternalPath()))
         self.assertTrue(view.pending_diff)
 
-    def test_subscribe_to_merge_proposal_events_flag_disabled(self):
-        # If the longpoll.merge_proposals.enabled flag is not enabled the user
-        # is *not* subscribed to events relating to the merge proposal.
-        bmp = self.factory.makeBranchMergeProposal()
-        view = create_initialized_view(bmp, '+index', current_request=True)
-        cache = IJSONRequestCache(view.request)
-        self.assertNotIn("longpoll", cache.objects)
-        self.assertNotIn("merge_proposal_event_key", cache.objects)
-
-    def test_subscribe_to_merge_proposal_events_flag_enabled(self):
-        # If the longpoll.merge_proposals.enabled flag is enabled the user is
-        # subscribed to events relating to the merge proposal.
-        bmp = self.factory.makeBranchMergeProposal()
-        self.useContext(feature_flags())
-        set_feature_flag('longpoll.merge_proposals.enabled', 'enabled')
-        view = create_initialized_view(bmp, '+index', current_request=True)
-        cache = IJSONRequestCache(view.request)
-        self.assertIn("longpoll", cache.objects)
-        self.assertIn("merge_proposal_event_key", cache.objects)
-
     def test_description_is_meta_description(self):
         description = (
             "I'd like to make the bmp description appear as the meta "
@@ -2076,20 +2056,6 @@
         browser = self.getViewBrowser(bmp)
         assert 'unf_pbasyvpgf' in browser.contents
 
-    def test_pending_diff_message_with_longpoll_enabled(self):
-        # If the longpoll feature flag is enabled then the message
-        # displayed for a pending diff indicates that it'll update
-        # automatically. See also
-        # lib/lp/code/stories/branches/xx-branchmergeproposals.txt
-        self.useContext(feature_flags())
-        set_feature_flag('longpoll.merge_proposals.enabled', 'enabled')
-        bmp = self.factory.makeBranchMergeProposal()
-        browser = self.getViewBrowser(bmp)
-        self.assertIn(
-            "An updated diff is being calculated and will appear "
-                "automatically when ready.",
-            browser.contents)
-
     def test_short_conversation_comments_not_truncated(self):
         """Short comments should not be truncated."""
         comment = self.factory.makeCodeReviewComment(body='x y' * 100)

=== removed file 'lib/lp/code/javascript/branchmergeproposal.updater.js'
--- lib/lp/code/javascript/branchmergeproposal.updater.js	2014-03-31 19:40:35 +0000
+++ lib/lp/code/javascript/branchmergeproposal.updater.js	1970-01-01 00:00:00 +0000
@@ -1,263 +0,0 @@
-/* Copyright 2011 Canonical Ltd.  This software is licensed under the
- * GNU Affero General Public License version 3 (see the file LICENSE).
- *
- * Code for updating the diff when a new version is available.
- *
- * @module lp.code.branchmergeproposal.updater
- * @requires node, lp.client
- */
-
-YUI.add('lp.code.branchmergeproposal.updater', function(Y) {
-
-var namespace = Y.namespace('lp.code.branchmergeproposal.updater');
-
-function UpdaterWidget(config) {
-    UpdaterWidget.superclass.constructor.apply(this, arguments);
-}
-
-Y.mix(UpdaterWidget, {
-
-    NAME: 'updaterWidget',
-
-    ATTRS: {
-
-        /**
-         * The LP client to use. If none is provided, one will be
-         * created during initialization.
-         *
-         * @attribute lp_client
-         */
-        lp_client: {
-            value: null
-        },
-
-        /**
-         * The summary node.
-         *
-         * @attribute summary_node
-         */
-        summary_node: {
-            value: null,
-            writeOnce: "initOnly"
-        },
-
-        /**
-         * Whether or not this MP is still 'pending'.
-         *
-         * @attribute pending
-         * @readOnly
-         */
-        pending: {
-            readOnly: true,
-            getter: function() {
-                return !Y.Lang.isValue(
-                    this.get('srcNode').one('.diff-content'));
-            }
-        },
-
-        /**
-         * The HTML code for the stats diff.
-         *
-         * @attribute diff_stats
-         */
-        diff_stats: {
-            getter: function() {
-                var summary_node = this.get('summary_node');
-                if (!Y.Lang.isValue(summary_node) ||
-                    !Y.Lang.isValue(summary_node.one(
-                         '#summary-row-b-diff'))) {
-                    return null;
-                }
-                return summary_node.one(
-                    '#summary-row-b-diff').one('td').get('innerHTML');
-            },
-            setter: function(value) {
-               this._setup_diff_stats_container();
-               var container = this.get(
-                   'summary_node').one('#summary-row-b-diff').one('td');
-               container.set('innerHTML', value);
-            }
-        }
-    }
-
-});
-
-Y.extend(UpdaterWidget, Y.Widget, {
-
-    /*
-     * The initializer method that is called from the base Plugin class.
-     *
-     * @method initializer
-     * @protected
-     */
-    initializer: function(cfg){
-        // If we have not been provided with a Launchpad Client, then
-        // create one now:
-        if (null === this.get("lp_client")){
-            // Create our own instance of the LP client.
-            this.set("lp_client", new Y.lp.client.Launchpad());
-        }
-        this.set('summary_node', cfg.summary_node);
-    },
-
-    /*
-     * Set the proper icon to indicate the diff is updating.
-     *
-     * @method set_status_updating
-     */
-    set_status_updating: function() {
-       this.cleanup_status();
-       this._set_status('spinner', 'Update in progress.');
-    },
-
-    /*
-     * Set the proper icon to indicate the diff will be updated when the
-     * new version is available.
-     *
-     * @method set_status_longpolling
-     */
-    set_status_longpolling: function() {
-       this.cleanup_status();
-       this._set_status(
-           'longpoll_loading',
-           'The diff will be updated as soon as a new version is available.');
-    },
-
-    /*
-     * Set the proper icon to indicate that the diff update is broken.
-     *
-     * @method set_status_longpollerror
-     */
-    set_status_longpollerror: function() {
-       this.cleanup_status();
-       this._set_status(
-           'longpoll_error',
-           'Diff update error, please reload to see the changes.');
-    },
-
-    /*
-     * Add a status image to the diff title.
-     *
-     * @method _set_status
-     */
-    _set_status: function(image_name, title) {
-       var image = Y.Node.create('<img />')
-           .set('src', '/@@/' + image_name)
-           .set('title', title);
-       this.get('srcNode').one('h2').append(image);
-    },
-
-    /*
-     * Remove the status image to the diff title.
-     *
-     * @method cleanup_status
-     */
-    cleanup_status: function() {
-        this._setup_diff_container();
-        this.get('srcNode').all('h2 img').remove();
-    },
-
-    /*
-     * Add a row in the page summary table to display the diff stats
-     * if needed.
-     *
-     * @method _setup_diff_stats_container
-     */
-     _setup_diff_stats_container: function() {
-        if (!Y.Lang.isValue(this.get('diff_stats'))) {
-            var summary_node = this.get('summary_node');
-            var diff_stats = Y.Node.create('<tr />')
-                .set('id', 'summary-row-b-diff')
-                .append(Y.Node.create('<th />')
-                    .set("text", "Diff against target:"))
-                .append(Y.Node.create('<td />'));
-            summary_node.one(
-                '#summary-row-9-target-branch').insert(diff_stats, 'after');
-        }
-     },
-
-    /*
-     * Populate the widget with the required nodes to display the diff
-     * if needed.
-     *
-     * @method _setup_diff_container
-     */
-    _setup_diff_container: function() {
-        if (this.get('pending')) {
-            // Cleanup.get('srcNode').
-            this.get('srcNode').empty();
-            // Create the diff container.
-            var review_diff = Y.Node.create('<div />')
-                .set('id', 'review-diff')
-                .append(Y.Node.create('<h2 />')
-                    .set("text", "Preview Diff "))
-                .append(Y.Node.create('<div />')
-                    .addClass("diff-navigator"))
-                .append(Y.Node.create('<div />')
-                    .addClass("diff-content"));
-            this.get('srcNode').append(review_diff);
-        }
-    },
-
-    /*
-     * Update the page diff stats.
-     *
-     * @method update
-     */
-    update: function() {
-        this.update_stats();
-    },
-
-    /*
-     * Update the diff stats with the last version.
-     *
-     * @method update_stats
-     */
-    update_stats: function() {
-        var self = this;
-        var config = {
-            on: {
-                success: function(diff_stats) {
-                    self.set('diff_stats', diff_stats);
-                    // (re)connect the js scroller link.
-                    Y.lp.code.branchmergeproposal.reviewcomment.link_scroller(
-                        '#proposal-summary a.diff-link', '#review-diff');
-                    var node = self.get('summary_node');
-                    Y.lp.anim.green_flash({node: node}).run();
-                    self.fire(self.NAME + '.updated');
-                },
-                failure: function() {
-                    var node = self.get('summary_node');
-                    Y.lp.anim.red_flash({node: node}).run();
-                },
-                start: function() {
-                    self.set_status_updating();
-                },
-                end: function() {
-                    self.cleanup_status();
-                }
-            }
-        };
-        var mp_uri = LP.cache.context.web_link;
-        this.get('lp_client').get(mp_uri + "/++diff-stats", config);
-    }
-
-});
-
-/*
- * Export UpdaterWidget.
- */
-namespace.UpdaterWidget = UpdaterWidget;
-
-/*
- * Returns true if the event fired means that the preview_diff field of the
- * MP has been updated.
- *
- */
-namespace.is_mp_diff_updated = function(event_data) {
-    return (event_data.what === "modified" &&
-        event_data.edited_fields.indexOf("preview_diff") >= 0);
-};
-
-}, '0.1', {requires: ['node', 'lp.client', 'lp.anim',
-                      'lp.code.branchmergeproposal.reviewcomment']});

=== removed file 'lib/lp/code/javascript/tests/test_branchmergeproposal.updater.html'
--- lib/lp/code/javascript/tests/test_branchmergeproposal.updater.html	2012-10-26 09:54:28 +0000
+++ lib/lp/code/javascript/tests/test_branchmergeproposal.updater.html	1970-01-01 00:00:00 +0000
@@ -1,98 +0,0 @@
-<!DOCTYPE html>
-<!--
-Copyright 2012 Canonical Ltd.  This software is licensed under the
-GNU Affero General Public License version 3 (see the file LICENSE).
--->
-
-<html>
-  <head>
-      <title>Test branchmergeproposal</title>
-
-      <!-- YUI and test setup -->
-      <script type="text/javascript"
-              src="../../../../../build/js/yui/yui/yui.js">
-      </script>
-      <link rel="stylesheet"
-      href="../../../../../build/js/yui/console/assets/console-core.css" />
-      <link rel="stylesheet"
-      href="../../../../../build/js/yui/test-console/assets/skins/sam/test-console.css" />
-      <link rel="stylesheet"
-      href="../../../../../build/js/yui/test/assets/skins/sam/test.css" />
-
-      <script type="text/javascript"
-              src="../../../../../build/js/lp/app/testing/testrunner.js"></script>
-
-      <link rel="stylesheet" href="../../../app/javascript/testing/test.css" />
-
-      <!-- Dependencies -->
-      <script type="text/javascript"
-          src="../../../../../build/js/lp/..."></script>
-      <script type="text/javascript"
-          src="../../../../../build/js/lp/app/client.js"></script>
-      <script type="text/javascript"
-          src="../../../../../build/js/lp/app/lp.js"></script>
-      <script type="text/javascript"
-          src="../../../../../build/js/lp/app/anim/anim.js"></script>
-      <script type="text/javascript"
-          src="../../../../../build/js/lp/app/extras/extras.js"></script>
-      <script type="text/javascript"
-          src="../../../../../build/js/lp/app/testing/mockio.js"></script>
-      <script type="text/javascript"
-          src="../../../../../build/js/lp/code/branchmergeproposal.reviewcomment.js"></script>
-
-      <!-- The module under test. -->
-      <script type="text/javascript" src="../branchmergeproposal.updater.js"></script>
-
-      <!-- Any css assert for this module. -->
-      <!-- <link rel="stylesheet" href="../assets/branchmergeproposal-core.css" /> -->
-
-      <!-- The test suite. -->
-      <script type="text/javascript" src="test_branchmergeproposal.updater.js"></script>
-
-      <!-- expected variable -->
-      <script type="text/javascript">
-        var LP = {
-            cache: {},
-            links: {}
-        };
-      </script>
-
-    </head>
-    <body class="yui3-skin-sam">
-        <ul id="suites">
-            <!-- <li>lp.large_indicator.test</li> -->
-            <li>lp.branchmergeproposal.updater.test</li>
-        </ul>
-        <div id="placeholder" style="display:none;">
-        </div>
-
-        <script type="text/x-template" id="pending-mp">
-          <table id="proposal-summary">
-            <tr id="summary-row-9-target-branch">
-              <th>Merge into:</th>
-              <td>
-                <a href="/~me/project/branch"
-                   class="sprite branch">lp://dev/~me/project/branch</a>
-              </td>
-            </tr>
-          </table>
-          <div id="diff-area">
-            <div class="pending-update" id="diff-pending-update">
-              <h3>Updating diff...</h3>
-              <p>An updated diff will be available in a few minutes.</p>
-            </div>
-          </div>
-        </script>
-
-        <script type="text/x-template" id="current-mp">
-          <div id="diff-area">
-            <div id="review-diff">
-              <h2>Preview Diff</h2>
-              <div class="diff-content">Example diff</div>
-            </div>
-          </div>
-        </script>
-
-
-    </body>
-</html>

=== removed file 'lib/lp/code/javascript/tests/test_branchmergeproposal.updater.js'
--- lib/lp/code/javascript/tests/test_branchmergeproposal.updater.js	2014-03-31 19:40:35 +0000
+++ lib/lp/code/javascript/tests/test_branchmergeproposal.updater.js	1970-01-01 00:00:00 +0000
@@ -1,162 +0,0 @@
-/* Copyright 2011 Canonical Ltd.  This software is licensed under the
- * GNU Affero General Public License version 3 (see the file LICENSE).
- *
- * Tests for lp.code.branchmergeproposal.updater.
- *
- */
-YUI.add('lp.branchmergeproposal.updater.test', function (Y) {
-var module = Y.lp.code.branchmergeproposal.updater;
-var UpdaterWidget = module.UpdaterWidget;
-
-
-var tests = Y.namespace('lp.branchmergeproposal.updater.test');
-tests.suite = new Y.Test.Suite("BranchMergeProposal Updater Tests");
-
-/*
- * Tests for when the updater is built on top of a pending diff.
- *
- */
-
-var pending_mp = Y.one('#pending-mp').getContent();
-
-tests.suite.add(new Y.Test.Case({
-
-    name: 'branchmergeproposal-updater-pending-tests',
-
-    setUp: function() {
-        Y.one("#placeholder")
-            .empty()
-            .append(Y.Node.create(pending_mp));
-        var diff_area = Y.one('#diff-area');
-        var summary_node = Y.one('#proposal-summary');
-        this.updater = new UpdaterWidget(
-            {srcNode: diff_area, summary_node: summary_node});
-
-        LP.cache.context = {
-            web_link: "https://code.launchpad.dev/~foo/bar/foobr/+merge/123"};
-
-    },
-
-    tearDown: function() {
-        this.updater.destroy();
-    },
-
-    test_default_values: function() {
-        Y.Assert.isTrue(this.updater.get('pending'));
-        Y.Assert.isNull(this.updater.get('diff_stats'));
-    },
-
-    test__setup_diff_container: function() {
-        this.updater._setup_diff_container();
-        Y.Assert.isFalse(this.updater.get('pending'));
-        Y.Assert.areEqual(
-            "Preview Diff ",
-            this.updater.get(
-                'srcNode').one('#review-diff h2').get('text'));
-        Y.Assert.areEqual(
-            "",
-            this.updater.get(
-                'srcNode').one('.diff-content').get('text'));
-    },
-
-    test__setup_diff_stats_container: function() {
-        Y.Assert.isNull(this.updater.get('diff_stats'));
-        this.updater._setup_diff_stats_container();
-        Y.Assert.areEqual('', this.updater.get('diff_stats'));
-    },
-
-    test_set_diff_stats: function() {
-        this.updater.set('diff_stats', '13 lines (+4/-0) 1 file modified');
-        Y.Assert.areEqual(
-            '13 lines (+4/-0) 1 file modified',
-            this.updater.get('diff_stats'));
-    },
-
-    test_set_status_updating: function() {
-        this.updater.set_status_updating();
-        Y.Assert.areEqual(
-            '/@@/spinner',
-            Y.one('h2').one('img').getAttribute('src'));
-    },
-
-    test_set_status_longpolling: function() {
-        this.updater.set_status_longpolling();
-        Y.Assert.areEqual(
-            '/@@/longpoll_loading',
-            Y.one('h2').one('img').getAttribute('src'));
-    },
-
-    test_set_status_longpollerror: function() {
-        this.updater.set_status_longpollerror();
-        Y.Assert.areEqual(
-            '/@@/longpoll_error',
-            Y.one('h2').one('img').getAttribute('src'));
-    },
-
-    test_cleanup_status: function() {
-        this.updater._setup_diff_container();
-        this.updater.set_status_updating();
-        this.updater.cleanup_status();
-        Y.Assert.areEqual(
-            'Preview Diff ',
-            Y.one('h2').get('innerHTML'));
-    },
-
-    test_update_stats_success: function() {
-        var mockio = new Y.lp.testing.mockio.MockIo();
-        this.updater.get('lp_client').io_provider = mockio;
-        Y.Assert.isNull(this.updater.get('diff_stats'));
-        this.updater.update_stats();
-        mockio.success({
-            responseText: '13 lines (+4/-0) 1 file modified',
-            responseHeaders: {'Content-Type': 'text/html'}});
-
-        Y.Assert.areEqual(
-            '13 lines (+4/-0) 1 file modified',
-            this.updater.get('diff_stats'));
-    },
-
-    test_update_fires_event: function() {
-        var fired = false;
-        var mockio = new Y.lp.testing.mockio.MockIo();
-        this.updater.get('lp_client').io_provider = mockio;
-        this.updater.on(this.updater.NAME + '.updated', function() {
-            fired = true;
-        });
-        this.updater.update();
-        mockio.success({
-            responseText: '13 lines (+4/-0) 1 file modified',
-            responseHeaders: {'Content-Type': 'text/html'}});
-        Y.Assert.isTrue(fired);
-    }
-
-}));
-
-
-tests.suite.add(new Y.Test.Case({
-
-    name: 'branchmergeproposal-updater-utilities',
-
-    test_is_mp_diff_updated_modified: function() {
-        var data = {what: 'modified', edited_fields: ['preview_diff']};
-        Y.Assert.isTrue(module.is_mp_diff_updated(data));
-    },
-
-    test_is_mp_diff_updater_deleted: function() {
-        var data = {what: 'deleted'};
-        Y.Assert.isFalse(module.is_mp_diff_updated(data));
-    },
-
-    test_is_mp_diff_updated_title_changed: function() {
-        var data = {what: 'modified', edited_fields: ['title']};
-        Y.Assert.isFalse(module.is_mp_diff_updated(data));
-    }
-
-}));
-
-
-}, '0.1', {
-    requires: ['lp.testing.runner', 'test', 'dump', 'test-console', 'node',
-               'lp.testing.mockio', 'event',
-               'lp.code.branchmergeproposal.updater']
-});

=== modified file 'lib/lp/code/templates/branchmergeproposal-index.pt'
--- lib/lp/code/templates/branchmergeproposal-index.pt	2019-01-31 13:48:34 +0000
+++ lib/lp/code/templates/branchmergeproposal-index.pt	2019-04-16 14:35:10 +0000
@@ -185,13 +185,10 @@
     <div class="yui-g" tal:condition="python: not view.show_diff_update_link and view.pending_diff">
       <div class="pending-update" id="diff-pending-update">
         <h3>Updating diff...</h3>
-        <p tal:condition="not: features/longpoll.merge_proposals.enabled">
+        <p>
           An updated diff will be available in a few minutes. Reload to see the
           changes.
         </p>
-        <p tal:condition="features/longpoll.merge_proposals.enabled">
-          An updated diff is being calculated and will appear automatically when ready.
-        </p>
       </div>
     </div>
     <div class="yui-g" tal:condition="view/show_diff_update_link">
@@ -226,7 +223,7 @@
   conf = <tal:status-config replace="view/status_config" />
   LPJS.use('io-base', 'lp.code.branchmergeproposal.reviewcomment',
           'lp.code.branchmergeproposal.status', 'lp.app.comment',
-          'lp.code.branchmergeproposal.updater', 'lp.app.widgets.expander',
+          'lp.app.widgets.expander',
           'lp.code.branch.revisionexpander',
           'lp.code.branchmergeproposal.inlinecomments', function(Y) {
 
@@ -252,23 +249,6 @@
             diffnav.render();
         }
 
-        if (Y.Lang.isValue(LP.cache.merge_proposal_event_key)) {
-            var upt = Y.lp.code.branchmergeproposal.updater;
-            var cfg = {
-                 srcNode: Y.one('#diff-area'),
-                 summary_node: Y.one('#proposal-summary')
-            };
-            var updater = new upt.UpdaterWidget(cfg);
-            Y.on(LP.cache.merge_proposal_event_key, function(data) {
-                if (upt.is_mp_diff_updated(data)) {
-                    updater.update();
-                }
-            });
-            updater.on(updater.NAME + '.updated', function() {
-                diffnav.render();
-            });
-        }
-
         LP.cache.comment_context = LP.cache.context;
         var cl = new Y.lp.app.comment.CommentList({
             comment_list_container: Y.one('#conversation')

=== modified file 'lib/lp/scripts/runlaunchpad.py'
--- lib/lp/scripts/runlaunchpad.py	2018-05-21 20:30:16 +0000
+++ lib/lp/scripts/runlaunchpad.py	2019-04-16 14:35:10 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -27,7 +27,6 @@
     )
 from lp.services.rabbit.server import RabbitServer
 from lp.services.sitesearch import bingtestservice
-from lp.services.txlongpoll.server import TxLongPollServer
 
 
 def make_abspath(path):
@@ -237,28 +236,6 @@
         self.useFixture(self.server)
 
 
-class TxLongPollService(Service):
-    """A TxLongPoll service."""
-
-    @property
-    def should_launch(self):
-        return config.txlongpoll.launch
-
-    def launch(self):
-        twistd_bin = os.path.join(config.root, 'bin', 'twistd')
-        broker_hostname, broker_port = as_host_port(
-            config.rabbitmq.host, None, None)
-        self.server = TxLongPollServer(
-            twistd_bin=twistd_bin,
-            frontend_port=config.txlongpoll.frontend_port,
-            broker_user=config.rabbitmq.userid,
-            broker_password=config.rabbitmq.password,
-            broker_vhost=config.rabbitmq.virtual_host,
-            broker_host=broker_hostname,
-            broker_port=broker_port)
-        self.useFixture(self.server)
-
-
 def stop_process(process):
     """kill process and BLOCK until process dies.
 
@@ -284,7 +261,6 @@
     'codebrowse': CodebrowseService(),
     'memcached': MemcachedService(),
     'rabbitmq': RabbitService(),
-    'txlongpoll': TxLongPollService(),
     }
 
 

=== modified file 'lib/lp/scripts/tests/test_runlaunchpad.py'
--- lib/lp/scripts/tests/test_runlaunchpad.py	2018-05-21 20:30:16 +0000
+++ lib/lp/scripts/tests/test_runlaunchpad.py	2019-04-16 14:35:10 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for runlaunchpad.py"""
@@ -156,10 +156,6 @@
         if config.rabbitmq.launch:
             expected.append(SERVICES['rabbitmq'])
 
-        # TxLongPoll may or may not be asked to run.
-        if config.txlongpoll.launch:
-            expected.append(SERVICES['txlongpoll'])
-
         expected = sorted(expected)
         self.assertEqual(expected, services)
 

=== modified file 'lib/lp/services/config/schema-lazr.conf'
--- lib/lp/services/config/schema-lazr.conf	2019-03-26 20:51:38 +0000
+++ lib/lp/services/config/schema-lazr.conf	2019-04-16 14:35:10 +0000
@@ -1601,16 +1601,6 @@
 # datatype: string
 virtual_host: none
 
-[txlongpoll]
-# Should TxLongPoll be launched by default?
-# datatype: boolean
-launch: False
-# The port at which TxLongPoll is listening.
-# datatype: string
-frontend_port: none
-# The uri that should be a proxy to the TxLongPoll server.
-uri: /+longpoll/
-
 [request_daily_builds]
 dbuser: request-daily-builds
 

=== modified file 'lib/lp/services/configure.zcml'
--- lib/lp/services/configure.zcml	2018-03-16 14:50:01 +0000
+++ lib/lp/services/configure.zcml	2019-04-16 14:35:10 +0000
@@ -1,4 +1,4 @@
-<!-- Copyright 2010-2011 Canonical Ltd.  This software is licensed under the
+<!-- Copyright 2010-2019 Canonical Ltd.  This software is licensed under the
      GNU Affero General Public License version 3 (see the file LICENSE).
 -->
 
@@ -16,7 +16,6 @@
   <include package=".inlinehelp" file="meta.zcml" />
   <include package=".job" />
   <include package=".librarian" />
-  <include package=".longpoll" />
   <include package=".mail" />
   <include package=".memcache" />
   <include package=".messages" />

=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py	2018-05-21 20:30:16 +0000
+++ lib/lp/services/features/flags.py	2019-04-16 14:35:10 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2018 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __all__ = [
@@ -171,13 +171,6 @@
      'None are enabled',
      '',
      ''),
-    ('longpoll.merge_proposals.enabled',
-     'boolean',
-     ('Enables the longpoll mechanism for merge proposals so that diffs, '
-      'for example, are updated in-page when they are ready.'),
-     '',
-     '',
-     ''),
     ('ajax.batch_navigator.enabled',
      'boolean',
      ('If true, batch navigators which have been wired to do so use ajax '

=== removed directory 'lib/lp/services/longpoll'
=== removed file 'lib/lp/services/longpoll/__init__.py'
=== removed directory 'lib/lp/services/longpoll/adapters'
=== removed file 'lib/lp/services/longpoll/adapters/__init__.py'
=== removed file 'lib/lp/services/longpoll/adapters/event.py'
--- lib/lp/services/longpoll/adapters/event.py	2015-07-09 12:18:51 +0000
+++ lib/lp/services/longpoll/adapters/event.py	1970-01-01 00:00:00 +0000
@@ -1,71 +0,0 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Long poll adapters."""
-
-__metaclass__ = type
-__all__ = [
-    "generate_event_key",
-    "LongPollEvent",
-    ]
-
-from zope.component import getUtility
-
-from lp.services.messaging.interfaces import IMessageSession
-
-
-def router_factory(event_key):
-    """Get a router for the given `event_key`."""
-    return getUtility(IMessageSession).getProducer(event_key)
-
-
-def generate_event_key(*components):
-    """Generate a suitable event name."""
-    if len(components) == 0:
-        raise AssertionError(
-            "Event keys must contain at least one component.")
-    return "longpoll.event.%s" % ".".join(
-        str(component) for component in components)
-
-
-class LongPollEvent:
-    """Base-class for event adapters.
-
-    Sub-classes need to define the `event_key` property and declare something
-    along the lines of::
-
-        @adapter(IAwesomeThing)
-        @implementer(ILongPollEvent)
-        class LongPollAwesomeThingEvent(LongPollEvent):
-            ...
-
-    Alternatively, use the `long_poll_event` class decorator::
-
-        @long_poll_event(IAwesomeThing)
-        class LongPollAwesomeThingEvent(LongPollEvent):
-            ...
-
-    In both cases the adapter should be registered in a `configure.zcml`
-    somewhere sensible::
-
-        <adapter factory=".adapters.LongPollAwesomeThingEvent" />
-
-    """
-
-    def __init__(self, source):
-        self.source = source
-
-    @property
-    def event_key(self):
-        """See `ILongPollEvent`."""
-        raise NotImplementedError(self.__class__.event_key)
-
-    def emit(self, **data):
-        """See `ILongPollEvent`.
-
-        The data will be updated with `event_key`, a copy of `self.event_key`.
-        """
-        event_key = self.event_key
-        data.update(event_key=event_key)
-        router = router_factory(event_key)
-        router.send(data)

=== removed file 'lib/lp/services/longpoll/adapters/storm.py'
--- lib/lp/services/longpoll/adapters/storm.py	2011-10-05 15:46:30 +0000
+++ lib/lp/services/longpoll/adapters/storm.py	1970-01-01 00:00:00 +0000
@@ -1,116 +0,0 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Long-poll life-cycle adapters."""
-
-from __future__ import absolute_import
-
-__metaclass__ = type
-__all__ = []
-
-from lazr.lifecycle.interfaces import (
-    IObjectCreatedEvent,
-    IObjectDeletedEvent,
-    IObjectModifiedEvent,
-    )
-from storm.base import Storm
-from storm.info import (
-    get_cls_info,
-    get_obj_info,
-    )
-from zope.component import adapter
-from zope.interface.interfaces import IAttribute
-from zope.security.proxy import removeSecurityProxy
-
-from lp.services.longpoll.adapters.event import (
-    generate_event_key,
-    LongPollEvent,
-    )
-from lp.services.longpoll.interfaces import (
-    ILongPollEvent,
-    long_poll_event,
-    )
-
-
-def gen_primary_key(model_instance):
-    """Generate the primary key values for the given model instance."""
-    cls_info = get_obj_info(model_instance).cls_info
-    for primary_key_column in cls_info.primary_key:
-        yield primary_key_column.__get__(model_instance)
-
-
-def get_primary_key(model_instance):
-    """Return the primary key for the given model instance.
-
-    If the primary key contains only one value it is returned, otherwise all
-    the primary key values are returned in a tuple.
-    """
-    pkey = tuple(gen_primary_key(model_instance))
-    return pkey[0] if len(pkey) == 1 else pkey
-
-
-@long_poll_event(Storm)
-class LongPollStormEvent(LongPollEvent):
-    """A `ILongPollEvent` for events of `Storm` objects.
-
-    This class knows how to construct a stable event key given a Storm object.
-    """
-
-    @property
-    def event_key(self):
-        """See `ILongPollEvent`.
-
-        Constructs the key from the table name and primary key values of the
-        Storm model object.
-        """
-        cls_info = get_obj_info(self.source).cls_info
-        return generate_event_key(
-            cls_info.table.name.lower(),
-            *gen_primary_key(self.source))
-
-
-@long_poll_event(type(Storm))
-class LongPollStormCreationEvent(LongPollEvent):
-    """A `ILongPollEvent` for events of `Storm` *classes*.
-
-    This class knows how to construct a stable event key given a Storm class.
-    """
-
-    @property
-    def event_key(self):
-        """See `ILongPollEvent`.
-
-        Constructs the key from the table name of the Storm class.
-        """
-        cls_info = get_cls_info(self.source)
-        return generate_event_key(
-            cls_info.table.name.lower())
-
-
-@adapter(Storm, IObjectCreatedEvent)
-def object_created(model_instance, object_event):
-    """Subscription handler for `Storm` creation events."""
-    model_class = removeSecurityProxy(model_instance).__class__
-    event = ILongPollEvent(model_class)
-    event.emit(what="created", id=get_primary_key(model_instance))
-
-
-@adapter(Storm, IObjectDeletedEvent)
-def object_deleted(model_instance, object_event):
-    """Subscription handler for `Storm` deletion events."""
-    event = ILongPollEvent(model_instance)
-    event.emit(what="deleted", id=get_primary_key(model_instance))
-
-
-@adapter(Storm, IObjectModifiedEvent)
-def object_modified(model_instance, object_event):
-    """Subscription handler for `Storm` modification events."""
-    edited_fields = object_event.edited_fields
-    if edited_fields is not None and len(edited_fields) != 0:
-        edited_field_names = sorted(
-            (field.__name__ if IAttribute.providedBy(field) else field)
-            for field in edited_fields)
-        event = ILongPollEvent(model_instance)
-        event.emit(
-            what="modified", edited_fields=edited_field_names,
-            id=get_primary_key(model_instance))

=== removed file 'lib/lp/services/longpoll/adapters/subscriber.py'
--- lib/lp/services/longpoll/adapters/subscriber.py	2015-07-09 12:18:51 +0000
+++ lib/lp/services/longpoll/adapters/subscriber.py	1970-01-01 00:00:00 +0000
@@ -1,58 +0,0 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Long poll adapters."""
-
-__metaclass__ = type
-__all__ = [
-    "generate_subscribe_key",
-    "LongPollApplicationRequestSubscriber",
-    ]
-
-from uuid import uuid4
-
-from lazr.restful.interfaces import IJSONRequestCache
-from zope.component import (
-    adapter,
-    getUtility,
-    )
-from zope.interface import implementer
-from zope.publisher.interfaces import IApplicationRequest
-
-from lp.services.config import config
-from lp.services.longpoll.interfaces import ILongPollSubscriber
-from lp.services.messaging.interfaces import IMessageSession
-
-
-def generate_subscribe_key():
-    """Generate a suitable new, unique, subscribe key."""
-    return "longpoll.subscribe.%s" % uuid4()
-
-
-@adapter(IApplicationRequest)
-@implementer(ILongPollSubscriber)
-class LongPollApplicationRequestSubscriber:
-
-    def __init__(self, request):
-        self.request = request
-
-    @property
-    def subscribe_key(self):
-        objects = IJSONRequestCache(self.request).objects
-        if "longpoll" in objects:
-            return objects["longpoll"]["key"]
-        return None
-
-    def subscribe(self, event):
-        cache = IJSONRequestCache(self.request)
-        if "longpoll" not in cache.objects:
-            cache.objects["longpoll"] = {
-                "uri": config.txlongpoll.uri,
-                "key": generate_subscribe_key(),
-                "subscriptions": [],
-                }
-        session = getUtility(IMessageSession)
-        subscribe_queue = session.getConsumer(self.subscribe_key)
-        producer = session.getProducer(event.event_key)
-        producer.associateConsumer(subscribe_queue)
-        cache.objects["longpoll"]["subscriptions"].append(event.event_key)

=== removed directory 'lib/lp/services/longpoll/adapters/tests'
=== removed file 'lib/lp/services/longpoll/adapters/tests/__init__.py'
=== removed file 'lib/lp/services/longpoll/adapters/tests/test_event.py'
--- lib/lp/services/longpoll/adapters/tests/test_event.py	2015-07-08 16:05:11 +0000
+++ lib/lp/services/longpoll/adapters/tests/test_event.py	1970-01-01 00:00:00 +0000
@@ -1,78 +0,0 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Long-poll event adapter tests."""
-
-__metaclass__ = type
-
-from zope.interface import implementer
-
-from lp.services.longpoll.adapters.event import (
-    generate_event_key,
-    LongPollEvent,
-    )
-from lp.services.longpoll.interfaces import ILongPollEvent
-from lp.services.longpoll.testing import (
-    capture_longpoll_emissions,
-    LongPollEventRecord,
-    )
-from lp.testing import TestCase
-from lp.testing.layers import LaunchpadFunctionalLayer
-from lp.testing.matchers import Contains
-
-
-@implementer(ILongPollEvent)
-class FakeEvent(LongPollEvent):
-
-    @property
-    def event_key(self):
-        return "event-key-%s" % self.source
-
-
-class TestLongPollEvent(TestCase):
-
-    layer = LaunchpadFunctionalLayer
-
-    def test_interface(self):
-        event = FakeEvent("source")
-        self.assertProvides(event, ILongPollEvent)
-
-    def test_event_key(self):
-        # event_key is not implemented in LongPollEvent; subclasses must
-        # provide it.
-        event = LongPollEvent("source")
-        self.assertRaises(NotImplementedError, getattr, event, "event_key")
-
-    def test_emit(self):
-        # LongPollEvent.emit() sends the given data to `event_key`.
-        event = FakeEvent("source")
-        event_data = {"hello": 1234}
-        with capture_longpoll_emissions() as log:
-            event.emit(**event_data)
-        expected_message = LongPollEventRecord(
-            event_key=event.event_key,
-            data=dict(event_data, event_key=event.event_key))
-        self.assertThat(log, Contains(expected_message))
-
-
-class TestFunctions(TestCase):
-
-    def test_generate_event_key_no_components(self):
-        self.assertRaises(
-            AssertionError, generate_event_key)
-
-    def test_generate_event_key(self):
-        self.assertEqual(
-            "longpoll.event.event-name",
-            generate_event_key("event-name"))
-        self.assertEqual(
-            "longpoll.event.source-name.event-name",
-            generate_event_key("source-name", "event-name"))
-        self.assertEqual(
-            "longpoll.event.type-name.source-name.event-name",
-            generate_event_key("type-name", "source-name", "event-name"))
-
-    def test_generate_event_key_stringifies_components(self):
-        self.assertEqual(
-            "longpoll.event.job.1234.COMPLETED",
-            generate_event_key("job", 1234, "COMPLETED"))

=== removed file 'lib/lp/services/longpoll/adapters/tests/test_storm.py'
--- lib/lp/services/longpoll/adapters/tests/test_storm.py	2012-01-01 02:58:52 +0000
+++ lib/lp/services/longpoll/adapters/tests/test_storm.py	1970-01-01 00:00:00 +0000
@@ -1,170 +0,0 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Long-poll event adapter tests."""
-
-__metaclass__ = type
-
-from lazr.lifecycle.event import (
-    ObjectCreatedEvent,
-    ObjectDeletedEvent,
-    ObjectModifiedEvent,
-    )
-from storm.base import Storm
-from storm.properties import Int
-from zope.event import notify
-from zope.interface import Attribute
-
-from lp.services.longpoll.adapters.storm import (
-    gen_primary_key,
-    get_primary_key,
-    )
-from lp.services.longpoll.interfaces import ILongPollEvent
-from lp.services.longpoll.testing import (
-    capture_longpoll_emissions,
-    LongPollEventRecord,
-    )
-from lp.testing import TestCase
-from lp.testing.layers import LaunchpadFunctionalLayer
-from lp.testing.matchers import Provides
-
-
-class FakeStormClass(Storm):
-
-    __storm_table__ = 'FakeTable'
-
-    id = Int(primary=True)
-
-
-class FakeStormCompoundPrimaryKeyClass(Storm):
-
-    __storm_table__ = 'FakeTableWithCompoundPrimaryKey'
-    __storm_primary__ = 'id1', 'id2'
-
-    id1 = Int()
-    id2 = Int()
-
-
-class TestFunctions(TestCase):
-
-    def test_gen_primary_key(self):
-        # gen_primary_key() returns an iterable of values from the model
-        # instance's primary key.
-        storm_object = FakeStormClass()
-        storm_object.id = 1234
-        self.assertEqual([1234], list(gen_primary_key(storm_object)))
-
-    def test_gen_primary_key_compound_key(self):
-        # gen_primary_key() returns an iterable of values from the model
-        # instance's primary key.
-        storm_object = FakeStormCompoundPrimaryKeyClass()
-        storm_object.id1 = 1234
-        storm_object.id2 = 5678
-        self.assertEqual([1234, 5678], list(gen_primary_key(storm_object)))
-
-    def test_get_primary_key(self):
-        # get_primary_key() returns the value of the model instance's primary
-        # key.
-        storm_object = FakeStormClass()
-        storm_object.id = 1234
-        self.assertEqual(1234, get_primary_key(storm_object))
-
-    def test_get_primary_key_compound_key(self):
-        # get_primary_key() returns a tuple of all the values in the model
-        # instance's primary key when the model uses a compound primary key.
-        storm_object = FakeStormCompoundPrimaryKeyClass()
-        storm_object.id1 = 1234
-        storm_object.id2 = 5678
-        self.assertEqual((1234, 5678), get_primary_key(storm_object))
-
-
-class TestStormLifecycle(TestCase):
-
-    layer = LaunchpadFunctionalLayer
-
-    def test_storm_event_adapter(self):
-        storm_object = FakeStormClass()
-        storm_object.id = 1234
-        event = ILongPollEvent(storm_object)
-        self.assertThat(event, Provides(ILongPollEvent))
-        self.assertEqual(
-            "longpoll.event.faketable.1234",
-            event.event_key)
-
-    def test_storm_creation_event_adapter(self):
-        event = ILongPollEvent(FakeStormClass)
-        self.assertThat(event, Provides(ILongPollEvent))
-        self.assertEqual(
-            "longpoll.event.faketable",
-            event.event_key)
-
-    def test_storm_object_created(self):
-        storm_object = FakeStormClass()
-        storm_object.id = 1234
-        with capture_longpoll_emissions() as log:
-            notify(ObjectCreatedEvent(storm_object))
-        expected = LongPollEventRecord(
-            "longpoll.event.faketable", {
-                "event_key": "longpoll.event.faketable",
-                "what": "created",
-                "id": 1234,
-                })
-        self.assertEqual([expected], log)
-
-    def test_storm_object_deleted(self):
-        storm_object = FakeStormClass()
-        storm_object.id = 1234
-        with capture_longpoll_emissions() as log:
-            notify(ObjectDeletedEvent(storm_object))
-        expected = LongPollEventRecord(
-            "longpoll.event.faketable.1234", {
-                "event_key": "longpoll.event.faketable.1234",
-                "what": "deleted",
-                "id": 1234,
-                })
-        self.assertEqual([expected], log)
-
-    def test_storm_object_modified(self):
-        storm_object = FakeStormClass()
-        storm_object.id = 1234
-        with capture_longpoll_emissions() as log:
-            object_event = ObjectModifiedEvent(
-                storm_object, storm_object, ("itchy", "scratchy"))
-            notify(object_event)
-        expected = LongPollEventRecord(
-            "longpoll.event.faketable.1234", {
-                "event_key": "longpoll.event.faketable.1234",
-                "what": "modified",
-                "edited_fields": ["itchy", "scratchy"],
-                "id": 1234,
-                })
-        self.assertEqual([expected], log)
-
-    def test_storm_object_modified_no_edited_fields(self):
-        # A longpoll event is not emitted unless edited_fields is populated.
-        storm_object = FakeStormClass()
-        storm_object.id = 1234
-        with capture_longpoll_emissions() as log:
-            notify(ObjectModifiedEvent(storm_object, storm_object, None))
-        self.assertEqual([], log)
-        with capture_longpoll_emissions() as log:
-            notify(ObjectModifiedEvent(storm_object, storm_object, ()))
-        self.assertEqual([], log)
-
-    def test_storm_object_modified_edited_fields_are_zope_attributes(self):
-        # The names of IAttribute fields in edited_fields are used in the
-        # longpoll event.
-        storm_object = FakeStormClass()
-        storm_object.id = 1234
-        with capture_longpoll_emissions() as log:
-            object_event = ObjectModifiedEvent(
-                storm_object, storm_object, ("foo", Attribute("bar")))
-            notify(object_event)
-        expected = LongPollEventRecord(
-            "longpoll.event.faketable.1234", {
-                "event_key": "longpoll.event.faketable.1234",
-                "what": "modified",
-                "edited_fields": ["bar", "foo"],
-                "id": 1234,
-                })
-        self.assertEqual([expected], log)

=== removed file 'lib/lp/services/longpoll/adapters/tests/test_subscriber.py'
--- lib/lp/services/longpoll/adapters/tests/test_subscriber.py	2015-07-08 16:05:11 +0000
+++ lib/lp/services/longpoll/adapters/tests/test_subscriber.py	1970-01-01 00:00:00 +0000
@@ -1,137 +0,0 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Long-poll subscriber adapter tests."""
-
-__metaclass__ = type
-
-from itertools import count
-
-from lazr.restful.interfaces import IJSONRequestCache
-from testtools.matchers import (
-    Not,
-    StartsWith,
-    )
-from zope.component import getUtility
-from zope.interface import implementer
-
-from lp.services.longpoll.adapters.subscriber import (
-    generate_subscribe_key,
-    LongPollApplicationRequestSubscriber,
-    )
-from lp.services.longpoll.interfaces import (
-    ILongPollEvent,
-    ILongPollSubscriber,
-    )
-from lp.services.messaging.interfaces import IMessageSession
-from lp.services.webapp.servers import LaunchpadTestRequest
-from lp.testing import TestCase
-from lp.testing.layers import LaunchpadFunctionalLayer
-from lp.testing.matchers import Contains
-
-
-@implementer(ILongPollEvent)
-class FakeEvent:
-
-    event_key_indexes = count(1)
-
-    def __init__(self):
-        self.event_key = "event-key-%d" % next(self.event_key_indexes)
-
-
-class TestLongPollSubscriber(TestCase):
-
-    layer = LaunchpadFunctionalLayer
-
-    def test_interface(self):
-        request = LaunchpadTestRequest()
-        subscriber = LongPollApplicationRequestSubscriber(request)
-        self.assertProvides(subscriber, ILongPollSubscriber)
-
-    def test_subscribe_key(self):
-        request = LaunchpadTestRequest()
-        subscriber = LongPollApplicationRequestSubscriber(request)
-        # A subscribe key is not generated yet.
-        self.assertIs(subscriber.subscribe_key, None)
-        # It it only generated on the first subscription.
-        subscriber.subscribe(FakeEvent())
-        subscribe_key = subscriber.subscribe_key
-        self.assertIsInstance(subscribe_key, str)
-        self.assertNotEqual(0, len(subscribe_key))
-        # It remains the same for later subscriptions.
-        subscriber.subscribe(FakeEvent())
-        self.assertEqual(subscribe_key, subscriber.subscribe_key)
-
-    def test_adapter(self):
-        request = LaunchpadTestRequest()
-        subscriber = ILongPollSubscriber(request)
-        self.assertIsInstance(
-            subscriber, LongPollApplicationRequestSubscriber)
-        # A difference subscriber is returned on subsequent adaptions, but it
-        # has the same subscribe_key.
-        subscriber2 = ILongPollSubscriber(request)
-        self.assertIsNot(subscriber, subscriber2)
-        self.assertEqual(subscriber.subscribe_key, subscriber2.subscribe_key)
-
-    def test_subscribe_queue(self):
-        # LongPollApplicationRequestSubscriber.subscribe() creates a new queue
-        # with a new unique name that is bound to the event's event_key.
-        request = LaunchpadTestRequest()
-        event = FakeEvent()
-        subscriber = ILongPollSubscriber(request)
-        subscriber.subscribe(event)
-        message = '{"hello": 1234}'
-        session = getUtility(IMessageSession)
-        routing_key = session.getProducer(event.event_key)
-        routing_key.send(message)
-        session.flush()
-        subscribe_queue = session.getConsumer(subscriber.subscribe_key)
-        self.assertEqual(
-            message, subscribe_queue.receive(timeout=5))
-
-    def test_json_cache_not_populated_on_init(self):
-        # LongPollApplicationRequestSubscriber does not put the name of the
-        # new queue into the JSON cache.
-        request = LaunchpadTestRequest()
-        cache = IJSONRequestCache(request)
-        self.assertThat(cache.objects, Not(Contains("longpoll")))
-        ILongPollSubscriber(request)
-        self.assertThat(cache.objects, Not(Contains("longpoll")))
-
-    def test_longpoll_uri_config(self):
-        # The JSON cache contains config.txlongpoll.uri.
-        self.pushConfig("txlongpoll", uri="/+longpoll/")
-        request = LaunchpadTestRequest()
-        cache = IJSONRequestCache(request)
-        ILongPollSubscriber(request).subscribe(FakeEvent())
-        self.assertEqual('/+longpoll/', cache.objects["longpoll"]["uri"])
-
-    def test_json_cache_populated_on_subscribe(self):
-        # To aid with debugging the event_key of subscriptions are added to
-        # the JSON cache.
-        request = LaunchpadTestRequest()
-        cache = IJSONRequestCache(request)
-        event1 = FakeEvent()
-        ILongPollSubscriber(request).subscribe(event1)  # Side-effects!
-        self.assertThat(cache.objects, Contains("longpoll"))
-        self.assertThat(cache.objects["longpoll"], Contains("key"))
-        self.assertThat(cache.objects["longpoll"], Contains("subscriptions"))
-        self.assertEqual(
-            [event1.event_key],
-            cache.objects["longpoll"]["subscriptions"])
-        # More events can be subscribed.
-        event2 = FakeEvent()
-        ILongPollSubscriber(request).subscribe(event2)
-        self.assertEqual(
-            [event1.event_key, event2.event_key],
-            cache.objects["longpoll"]["subscriptions"])
-
-
-class TestFunctions(TestCase):
-
-    def test_generate_subscribe_key(self):
-        subscribe_key = generate_subscribe_key()
-        expected_prefix = "longpoll.subscribe."
-        self.assertThat(subscribe_key, StartsWith(expected_prefix))
-        # The key contains a 36 character UUID.
-        self.assertEqual(len(expected_prefix) + 36, len(subscribe_key))

=== removed file 'lib/lp/services/longpoll/configure.zcml'
--- lib/lp/services/longpoll/configure.zcml	2011-10-05 15:14:53 +0000
+++ lib/lp/services/longpoll/configure.zcml	1970-01-01 00:00:00 +0000
@@ -1,15 +0,0 @@
-<!-- Copyright 2011 Canonical Ltd.  This software is licensed under the
-     GNU Affero General Public License version 3 (see the file LICENSE).
--->
-<configure
-    xmlns="http://namespaces.zope.org/zope";
-    xmlns:browser="http://namespaces.zope.org/browser";
-    xmlns:i18n="http://namespaces.zope.org/i18n";
-    i18n_domain="launchpad">
-    <adapter factory=".adapters.storm.LongPollStormEvent" />
-    <adapter factory=".adapters.storm.LongPollStormCreationEvent" />
-    <adapter factory=".adapters.subscriber.LongPollApplicationRequestSubscriber" />
-    <subscriber handler=".adapters.storm.object_created" />
-    <subscriber handler=".adapters.storm.object_deleted" />
-    <subscriber handler=".adapters.storm.object_modified" />
-</configure>

=== removed file 'lib/lp/services/longpoll/interfaces.py'
--- lib/lp/services/longpoll/interfaces.py	2011-09-20 19:04:19 +0000
+++ lib/lp/services/longpoll/interfaces.py	1970-01-01 00:00:00 +0000
@@ -1,64 +0,0 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Long-poll infrastructure interfaces."""
-
-__metaclass__ = type
-__all__ = [
-    "ILongPollEvent",
-    "ILongPollSubscriber",
-    "long_poll_event",
-    ]
-
-from zope.component import adapter
-from zope.interface import (
-    Attribute,
-    classImplements,
-    Interface,
-    )
-
-
-class ILongPollEvent(Interface):
-
-    source = Attribute("The event source.")
-
-    event_key = Attribute(
-        "The key with which events will be emitted. Should be predictable "
-        "and stable.")
-
-    def emit(**data):
-        """Emit the given data to `event_key`.
-
-        :param data: Any data structures that can be dumped as JSON.
-        """
-
-
-class ILongPollSubscriber(Interface):
-
-    subscribe_key = Attribute(
-        "The key which the subscriber must know in order to be able "
-        "to long-poll for subscribed events. Should be infeasible to "
-        "guess, a UUID for example.")
-
-    def subscribe(event):
-        """Subscribe to the given event.
-
-        :type event: ILongPollEvent
-        """
-
-
-def long_poll_event(source_spec):
-    """Class decorator to declare an `ILongPollEvent`.
-
-    :param source_spec: An interface or other specification understood by
-        `zope.component` (a plain class can be passed too) that defines the
-        source of an event. `IJob` or `storm.base.Storm` for example.
-    """
-    declare_adapter = adapter(source_spec)
-
-    def declare_event(cls):
-        classImplements(cls, ILongPollEvent)
-        declare_adapter(cls)
-        return cls
-
-    return declare_event

=== removed file 'lib/lp/services/longpoll/testing.py'
--- lib/lp/services/longpoll/testing.py	2011-09-23 16:36:56 +0000
+++ lib/lp/services/longpoll/testing.py	1970-01-01 00:00:00 +0000
@@ -1,57 +0,0 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Things that help with testing of longpoll."""
-
-__metaclass__ = type
-__all__ = [
-    "capture_longpoll_emissions",
-    "LongPollEventRecord",
-    ]
-
-from collections import namedtuple
-from contextlib import contextmanager
-from functools import partial
-
-from lp.services.longpoll.adapters import event
-
-
-LongPollEventRecord = namedtuple(
-    "LongPollEventRecord", ("event_key", "data"))
-
-
-class LoggingRouter:
-    """A test double for `IMessageProducer`.
-
-    Saves messages as `LongPollEventRecord` tuples to a log.
-
-    :param log: A callable accepting a single `LongPollEventRecord`.
-    :param routing_key: See `IMessageSession.getProducer`.
-    """
-
-    def __init__(self, log, routing_key):
-        self.log = log
-        self.routing_key = routing_key
-
-    def send(self, data):
-        record = LongPollEventRecord(self.routing_key, data)
-        self.log(record)
-
-
-@contextmanager
-def capture_longpoll_emissions():
-    """Capture longpoll emissions while this context is in force.
-
-    This returns a list in which `LongPollEventRecord` tuples will be
-    recorded, in the order they're emitted.
-
-    Note that normal event emission is *suppressed globally* while this
-    context is in force; *all* events will be stored in the log.
-    """
-    log = []
-    original_router_factory = event.router_factory
-    event.router_factory = partial(LoggingRouter, log.append)
-    try:
-        yield log
-    finally:
-        event.router_factory = original_router_factory

=== removed directory 'lib/lp/services/longpoll/tests'
=== removed file 'lib/lp/services/longpoll/tests/__init__.py'
=== removed file 'lib/lp/services/longpoll/tests/test_interfaces.py'
--- lib/lp/services/longpoll/tests/test_interfaces.py	2011-09-20 19:04:19 +0000
+++ lib/lp/services/longpoll/tests/test_interfaces.py	1970-01-01 00:00:00 +0000
@@ -1,33 +0,0 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Long-poll interface tests."""
-
-__metaclass__ = type
-
-from zope.component import adaptedBy
-from zope.interface import Interface
-
-from lp.services.longpoll.interfaces import (
-    ILongPollEvent,
-    long_poll_event,
-    )
-from lp.testing import TestCase
-
-
-class IEventSourceInterface(Interface):
-    """Test interface for an event source."""
-
-
-class TestLongPollInterfaces(TestCase):
-
-    def test_long_poll_event(self):
-        # long_poll_event is a class decorator that declares a class as an
-        # ILongPollEvent.
-        @long_poll_event(IEventSourceInterface)
-        class Something:
-            """An example event source."""
-        self.assertTrue(ILongPollEvent.implementedBy(Something))
-        self.assertEqual(
-            (IEventSourceInterface,),
-            adaptedBy(Something))

=== removed directory 'lib/lp/services/txlongpoll'
=== removed file 'lib/lp/services/txlongpoll/__init__.py'
=== removed file 'lib/lp/services/txlongpoll/server.py'
--- lib/lp/services/txlongpoll/server.py	2011-09-30 07:28:45 +0000
+++ lib/lp/services/txlongpoll/server.py	1970-01-01 00:00:00 +0000
@@ -1,31 +0,0 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""TxLongPoll server fixture."""
-
-__metaclass__ = type
-__all__ = [
-    'TxLongPollServer',
-    ]
-
-from textwrap import dedent
-
-from txlongpollfixture.server import TxLongPollFixture
-
-
-class TxLongPollServer(TxLongPollFixture):
-    """A TxLongPoll server fixture with Launchpad-specific config.
-
-    :ivar service_config: A snippet of .ini that describes the `txlongpoll`
-        configuration.
-    """
-
-    def setUp(self):
-        super(TxLongPollServer, self).setUp()
-        setattr(
-            self, 'service_config',
-            dedent("""\
-                [txlongpoll]
-                frontend_port: %d
-                """ % (
-                    self.config.frontend_port)))

=== removed directory 'lib/lp/services/txlongpoll/tests'
=== removed file 'lib/lp/services/txlongpoll/tests/__init__.py'
=== removed file 'lib/lp/services/txlongpoll/tests/test_server.py'
--- lib/lp/services/txlongpoll/tests/test_server.py	2017-09-23 03:13:41 +0000
+++ lib/lp/services/txlongpoll/tests/test_server.py	1970-01-01 00:00:00 +0000
@@ -1,38 +0,0 @@
-# Copyright 2011-2017 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Tests for lp.services.rabbit.TxLongPollServer."""
-
-__metaclass__ = type
-
-from ConfigParser import SafeConfigParser
-import os
-from StringIO import StringIO
-
-from lp.services.config import config
-from lp.services.txlongpoll.server import TxLongPollServer
-from lp.testing import TestCase
-from lp.testing.layers import RabbitMQLayer
-
-
-class TestTxLongPollServer(TestCase):
-
-    layer = RabbitMQLayer
-
-    def test_service_config(self):
-        # TxLongPollServer pokes some .ini configuration into its
-        # service_config attributes.
-        twistd_bin = os.path.join(config.root, 'bin', 'twistd')
-        fixture = self.useFixture(TxLongPollServer(
-            broker_user='guest', broker_password='guest', broker_vhost='/',
-            broker_port=123, frontend_port=None,
-            twistd_bin=twistd_bin))
-        service_config = SafeConfigParser()
-        service_config.readfp(StringIO(getattr(fixture, 'service_config')))
-        self.assertEqual(["txlongpoll"], service_config.sections())
-        # txlongpoll section
-        expected = {
-            "frontend_port": "%d" % fixture.config.frontend_port,
-            }
-        observed = dict(service_config.items("txlongpoll"))
-        self.assertEqual(expected, observed)

=== modified file 'setup.py'
--- setup.py	2018-07-16 10:51:04 +0000
+++ setup.py	2019-04-16 14:35:10 +0000
@@ -227,8 +227,6 @@
         'treq',
         'Twisted[conch,tls]',
         'txfixtures',
-        'txlongpoll',
-        'txlongpollfixture',
         'txpkgupload',
         'virtualenv-tools3',
         'wadllib',


Follow ups