I'm working on a chat bot and I want to deploy it to Azure Web App Services. The bot receives HTTP requests from the messaging platform, and it also runs in a while loop checking something it's designed to notify about. The HTTP part is handled by Flask and it works fine. But I can't get the loop to work alongside Flask. My (limited) understanding is I have to get two separate event loops to co-exist. The main one is occupied by Flask and I need to have a separate one for my notification checker. I tried all kinds of combinations of multiprocessing, asyncio and threading. Here's the latest version I ended up with: import threading from flask import Flask, jsonify import logging import sys import asyncio import datetime import os import telegram import time logging.basicConfig(level=logging.INFO, handlers=[logging.StreamHandler(sys.stdout)]) class Bot: def __init__(self): self.bot_token = os.getenv('TOKEN') self.chat_id = os.getenv('CHAT_ID') self.telegram_bot = telegram.Bot(token=self.bot_token) self.sleep = os.getenv('SLEEP') # set to 60 def run(self): while True: current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') message = f"It's {current_time} o'clock!\n\nSent from " + __file__ self.send_notification(message) time.sleep(int(self.sleep)) def send_notification(self, message): async def send_message(): await self.telegram_bot.send_message(chat_id=self.chat_id, text=message) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(send_message()) bot = Bot() app = Flask(__name__) # Simple endpoint that responds to Azure ping @app.route('/') def home(): return jsonify( hello='world', now=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') ), 200 if __name__ == '__main__': bot_thread = threading.Thread(target=bot.run) bot_thread.daemon = True # ChatGPT says this ensures the bot thread will stop when the main thread exits bot_thread.start() app.run(host=os.getenv("HTTP_HOST", "0.0.0.0"), port=int(os.getenv("HTTP_PORT"))) I tried the above code in the following environments: Local machine in PyCharm: works fine, it both 1. serves HTTP requests and 2. sends out a message every 60 seconds until I shut it down; Render.com, deployed as an experiment to a Web Service on their free tier: works fine, also both receives requests and sends out messages, but shuts down after 10-20 minutes because of the free tier limitation; Asure, deployed to a Web App Service on their free tier: runs and serves HTTP requests, but the bot does not work. No errors in logs. Checked all logs under https://.scm.azurewebsites.net/api/logs/docker. What I have on Azure is a Linux Web App on their free tier. { "type": "Microsoft.Web/sites", "kind": "app,linux", "properties": { "state": "Running", "usageState": "Normal", "siteScopedCertificatesEnabled": false, "afdEnabled": false, "siteProperties": { "properties": [ { "name": "LinuxFxVersion", "value": "PYTHON|3.13" }, { "name": "WindowsFxVersion", "value": null } ], "appSettings": null }, "availabilityState": "Normal", "csrs": [], "cers": null, "siteMode": null, "computeMode": null, "serverFarm": null, "reserved": true, "isXenon": false, "hyperV": false, "storageRecoveryDefaultState": "Running", "contentAvailabilityState": "Normal", "runtimeAvailabilityState": "Normal", "dnsConfiguration": {}, "vnetRouteAllEnabled": false, "containerAllocationSubnet": null, "useContainerLocalhostBindings": null, "vnetImagePullEnabled": false, "vnetContentShareEnabled": false, "outboundVnetRouting": null, "siteConfig": { "numberOfWorkers": 1, "defaultDocuments": null, "netFrameworkVersion": null, "phpVersion": null, "pythonVersion": null, "nodeVersion": null, "powerShellVersion": null, "linuxFxVersion": "PYTHON|3.13", "windowsFxVersion": null, "windowsConfiguredStacks": null, "requestTracingEnabled": null, "remoteDebuggingEnabled": null, "remoteDebuggingVersion": null, "httpLoggingEnabled": null, "azureMonitorLogCategories": null, "acrUseManagedIdentityCreds": false, "acrUserManagedIdentityID": null, "logsDirectorySizeLimit": null, "detailedErrorLoggingEnabled": null, "publishingUsername": null, "publishingPassword": null, "appSettings": null, "metadata": null, "connectionStrings": null, "machineKey": null, "handlerMappings": null, "documentRoot": null, "scmType": null, "use32BitWorkerProcess": null, "webSocketsEnabled": null, "alwaysOn": false, "javaVersion": null, "javaContainer": null, "javaContainerVersion": null, "appCommandLine": null, "managedPipelineMode": null, "virtualApplications": null, "winAuthAdminState": null, "winAuthTenantState": null, "customAppPoolIdentityAdminState": null, "customAppPoolIdentityTenantState": null, "runtimeADUser": null, "runtimeADUserPassword": null, "loadBalancing": null, "routingRules": null, "experiments": null, "limits": null, "autoHealEnabled": null, "autoHealRules": null, "tracingOptions": null, "vnetName": null, "vnetRouteAllEnabled": null, "vnetPrivatePortsCount": null, "publicNetworkAccess": null, "cors": null, "push": null, "apiDefinition": null, "apiManagementConfig": null, "autoSwapSlotName": null, "localMySqlEnabled": null, "managedServiceIdentityId": null, "xManagedServiceIdentityId": null, "keyVaultReferenceIdentity": null, "ipSecurityRestrictions": null, "ipSecurityRestrictionsDefaultAction": null, "scmIpSecurityRestrictions": null, "scmIpSecurityRestrictionsDefaultAction": null, "scmIpSecurityRestrictionsUseMain": null, "http20Enabled": true, "minTlsVersion": null, "minTlsCipherSuite": null, "scmMinTlsCipherSuite": null, "supportedTlsCipherSuites": null, "scmSupportedTlsCipherSuites": null, "scmMinTlsVersion": null, "ftpsState": null, "preWarmedInstanceCount": null, "functionAppScaleLimit": 0, "elasticWebAppScaleLimit": null, "healthCheckPath": null, "fileChangeAuditEnabled": null, "functionsRuntimeScaleMonitoringEnabled": null, "minimumElasticInstanceCount": 1, "azureStorageAccounts": null, "http20ProxyFlag": null, "sitePort": null, "antivirusScanEnabled": null, "storageType": null, "sitePrivateLinkHostEnabled": null, "clusteringEnabled": false }, "functionAppConfig": null, "daprConfig": null, "deploymentId": "<app>", "slotName": null, "trafficManagerHostNames": null, "sku": "Free", "scmSiteAlsoStopped": false, "targetSwapSlot": null, "hostingEnvironment": null, "hostingEnvironmentProfile": null, "clientAffinityEnabled": false, "clientAffinityProxyEnabled": false, "blockPathTraversal": false, "clientCertEnabled": false, "clientCertMode": "Required", "clientCertExclusionPaths": null, "hostNamesDisabled": false, "ipMode": "IPv4", "vnetBackupRestoreEnabled": false, "domainVerificationIdentifiers": null, "kind": "app,linux", "managedEnvironmentId": null, "workloadProfileName": null, "resourceConfig": null, "containerSize": 0, "dailyMemoryTimeQuota": 0, "suspendedTill": null, "siteDisabledReason": 0, "functionExecutionUnitsCache": null, "maxNumberOfWorkers": null, "cloningInfo": null, "hostingEnvironmentId": null, "slotSwapStatus": null, "httpsOnly": false, "endToEndEncryptionEnabled": false, "functionsRuntimeAdminIsolationEnabled": false, "redundancyMode": "None", "inProgressOperationId": null, "geoDistributions": null, "privateEndpointConnections": [], "publicNetworkAccess": "Enabled", "buildVersion": null, "targetBuildVersion": null, "migrationState": null, "eligibleLogCategories": "AppServiceAppLogs,AppServiceAuditLogs,AppServiceConsoleLogs,AppServiceHTTPLogs,AppServiceIPSecAuditLogs,AppServicePlatformLogs,ScanLogs,AppServiceAuthenticationLogs", "inFlightFeatures": [ "SiteContainers" ], "storageAccountRequired": false, "virtualNetworkSubnetId": null, "keyVaultReferenceIdentity": "SystemAssigned", "autoGeneratedDomainNameLabelScope": null, "defaultHostNameScope": "Global", "privateLinkIdentifiers": null, "sshEnabled": true }, "apiVersion": "2022-03-01" } Is there a way to get it to work on Azure Web App Services within Free Tier? Is there a specific way I should utilise to get a separate thread/process to get it to work on Azure? Or should I just do it differently? I do not necessarily have to have it run a While True loop, I just need the app to check something every minute while listening to incoming requests. One workaround I can think of is just have a separate endpoint that does the check and then just trigger it every minute via some external cron, but that seems way too cumbersome for the task, when the code itself should just be able to run a loop internally. (On the other hand, this can have a positive effect of keeping the Azure app alive, as their free tier will shut it down after 20 minutes similarly to Render.com. If I got the loop to work, my plan was to have the bot send a request to itself e.g. on every 10th check.) Continue reading...